Laravel CRUD Operation with File Upload
Welcome to the 11th chapter of Being an Artisanary. In show CRUD(create-read-edit-delete) operation with file upload. So if you already complete the previous chapters/sections, then you're good to go, if not my recommendation would be please complete the previous chapters. Because we'll use the same old repository.
Note: Tested on Laravel 10.0
Table of Contents
- Install Image Intervention for Image Processing
- Create and Setup a Repository
- Create and Setup Observer
- Create and Setup the Controller
- Setup Model and Migration
- Define Routes
- Create and Setup View
- Output
Install Image Intervention for Image Processing
Image intervention is one of the most used packages for image processing. For installing image intervention fire the below command in the terminal.
composer require intervention/image
And it'll be enough, no need to do anything.
Create and Setup a Repository
At first, we'll create a repository class called BlogRepository.php where we'll write our all database logic so that we can use the same query everywhere.
<?php
namespace App\Repositories;
use App\Models\Blog;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
class BlogRepository
{
public function all()
{
return Blog::latest()->paginate();
}
protected function createData($data)
{
if (array_key_exists('image', $data)) {
$image = $data['image'];
$image_name = Str::uuid() . '.' . $image->getClientOriginalExtension();
$destinationPath = public_path('uploads/');
if (!is_dir($destinationPath)) {
mkdir($destinationPath, 0777, true);
}
Image::make($data['image'])->resize(50,null, function ($constraint) {
$constraint->aspectRatio();
})->save($destinationPath.$image_name);
$data['image'] = 'uploads/'.$image_name;
}
return $data;
}
public function store($data)
{
return Blog::create($this->createData($data));
}
public function find($id)
{
return Blog::find($id);
}
public function update($id,$data)
{
$blog = $this->find($id);
if (array_key_exists('image', $data) && $blog->image) {
$image_path = public_path($blog->image);
if (file_exists($image_path)) {
unlink($image_path);
}
}
return $blog->update($this->createData($data));
}
public function destroy($id)
{
return Blog::destroy($id);
}
}
Create and Setup Observer
We'll already discussed what is Observer and why we use observer, so I'll not repeat here.
<?php
namespace App\Observers;
use App\Models\Blog;
use Illuminate\Support\Str;
class BlogObserver
{
public function creating(Blog $blog)
{
$blog->slug = Str::slug($blog->title);
}
public function created(Blog $blog): void
{
$blog->unique_id = 'PR-'.$blog->id;
$blog->save();
}
public function updating(Blog $blog): void
{
$blog->slug = Str::slug($blog->title);
}
public function updated(Blog $blog): void
{
//
}
public function deleted(Blog $blog): void
{
$image_path = public_path($blog->image);
if (file_exists($image_path)) {
unlink($image_path);
}
}
public function restored(Blog $blog): void
{
//
}
public function forceDeleted(Blog $blog): void
{
//
}
}
Create and Setup the Controller
Then, we'll create a controller called BlogController.php where we'll write our logic or insert the data. So, fire the below command in the terminal.
php artisan make:controller BlogController -r
It'll create a file under app\Http\Controllers called BlogController.php. Now open the file and replace it with the below codes.
<?php
namespace App\Http\Controllers;
use App\Http\Requests\BlogRequest;
use App\Repositories\BlogRepository;
class BlogController extends Controller
{
protected $blogRepository;
public function __construct(BlogRepository $blogRepository)
{
$this->blogRepository = $blogRepository;
}
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse
{
try {
$data = [
'blogs' => $this->blogRepository->all()
];
return view('backend.blogs.index', $data);
} catch (\Exception $e) {
return back()->with('error', $e->getMessage());
}
}
public function create(): \Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\Foundation\Application
{
return view('backend.blogs.form');
}
public function store(BlogRequest $request)
{
try {
$this->blogRepository->store($request->all());
return redirect()->route('blogs.index')->with('success', 'Blog created successfully');
} catch (\Exception $e) {
return back()->withInput()->with('error', $e->getMessage());
}
}
public function edit(string $id): \Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse
{
try {
$data = [
'edit' => $this->blogRepository->find($id)
];
return view('backend.blogs.form', $data);
} catch (\Exception $e) {
return back()->with('error', $e->getMessage());
}
}
public function update(BlogRequest $request, $id): \Illuminate\Http\RedirectResponse
{
try {
$this->blogRepository->update($id, $request->all());
return redirect()->route('blogs.index')->with('success', 'Blog updated successfully');
} catch (\Exception $e) {
return back()->withInput()->with('error', $e->getMessage());
}
}
public function destroy(string $id): \Illuminate\Http\RedirectResponse
{
try {
$this->blogRepository->destroy($id);
return redirect()->route('blogs.index')->with('success', 'Blog deleted successfully');
} catch (\Exception $e) {
return back()->with('error', $e->getMessage());
}
}
}
Setup Model and Migration
Now we'll set up our model and migration file. And we also discuss each attribute that we going to use there in the previous lecture. So I don't want to repeat it here.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Blog extends Model
{
use HasFactory;
protected $fillable = [
'title',
'slug',
'unique_id',
'description',
'image',
'user_id',
];
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
public function scopeActive($query)
{
return $query->where('status', 1);
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('blogs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users');
$table->string('title');
$table->string('slug');
$table->string('unique_id')->nullable();
$table->text('description');
$table->string('image');
$table->boolean('status')->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('blogs');
}
};
Define Routes
Put these routes in web.php.
Route::resource('blogs', BlogController::class)->except(['show']);
The whole file will look like the below
<?php
use App\Http\Controllers\BlogController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::resource('blogs', BlogController::class)->except(['show']);
});
require __DIR__.'/auth.php';
Create and Setup View
So at first, we'll create a blade file where we put our flash messages which we discuss in our previous chapter. And later we'll just @include() on every page.
@if(session()->has('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>Success!</strong> {{ session()->get('success') }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
@endif
@if(session()->has('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>Error!</strong> {{ session()->get('error') }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
@endif
@if(session()->has('warning'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>Warning!</strong> {{ session()->get('warning') }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
@endif
@if(session()->has('info'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>Info!</strong> {{ session()->get('info') }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
@endif
Now we'll set up our form. We'll use this form both for creating and updating the blog.
@extends('backend.layouts.master')
@section('title', isset($edit) ?__('Edit Blog') : __('Create Blog'))
@section('content')
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0">Blog</h1>
</div><!-- /.col -->
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item active">{{ isset($edit) ?__('Edit Blog') : __('Create Blog') }}</li>
</ol>
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content-header -->
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@include('layouts.alert')
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ isset($edit) ?__('Edit Blog') : __('Create Blog') }}</h3>
</div>
@php
$route = isset($edit) ? route('blogs.update',$edit->id) : route('blogs.store');
@endphp
<!-- /.card-header -->
<form action="{{ $route }}" method="post" enctype="multipart/form-data">@csrf
@isset($edit)
@method('PUT')
@endisset
<input type="hidden" name="user_id" value="{{ isset($edit) ? $edit->user_id : auth()->id() }}">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<label for="title">{{ __('Title') }}</label>
<input name="title" id="title" type="text"
class="form-control" placeholder="{{ __('Enter Title') }}" value="{{ old('title',@$edit->title) }}">
<span class="text-danger">{{ $errors->first('title') }}</span>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label for="image">{{ __('Image') }}</label>
<input name="image" id="image" type="file"
class="form-control">
<span class="text-danger">{{ $errors->first('image') }}</span>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label for="status">{{ __('Status') }}</label>
<select name="status" id="status"
class="form-control text-capitalize">
<option value="1" {{ old('title',@$edit->title) == 1 ? 'selected' : '' }}>{{ __('Active') }}</option>
<option value="0" {{ old('title',@$edit->title) == 0 ? 'selected' : '' }}>{{ __('Inactive') }}</option>
</select>
<span class="text-danger error">{{ $errors->first('status') }}</span>
</div>
</div>
<div class="col-lg-12">
<div class="form-group">
<label for="summernote">{{ __('Description') }}</label>
<textarea id="summernote" name="description" class="summernote">{{ old('description',@$edit->description) }}</textarea>
<span class="text-danger error">{{ $errors->first('description') }}</span>
</div>
</div>
</div>
</div>
<div class="card-footer text-right">
<button type="submit" class="btn btn-primary">{{ __('Save')}}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
@endsection
@push('js')
<script>
$('#summernote').summernote({
placeholder: 'Enter Description',
tabsize: 2,
height: 250
});
</script>
@endpush
And here we'll show our blog list
@extends('backend.layouts.master')
@section('title',__('Blog List'))
@section('content')
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0">Blogs</h1>
</div><!-- /.col -->
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item active">Blog List</li>
</ol>
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content-header -->
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
@include('layouts.alert')
<div class="card">
<div class="card-header">
<h3 class="card-title">{{__('Blogs')}}</h3>
</div>
<!-- /.card-header -->
<div class="card-body p-0">
<table class="table table-sm">
<thead>
<tr>
<th style="width: 10px">#</th>
<th>{{__('Image')}}</th>
<th>{{__('Title')}}</th>
<th>{{__('Status')}}</th>
<th style="width: 40px">{{ __('Action')}}</th>
</tr>
</thead>
<tbody>
@forelse($blogs as $key=> $blog)
<tr>
<td>{{ $blogs->firstItem() + $key }}</td>
<td>{{ $blog->title }}</td>
<td><img src="{{ asset($blog->image) }}" alt="{{ $blog->title }}"></td>
<td><span @class([
'badge',
'badge-success' => $blog->status == 1,
'badge-danger' => $blog->status == 0,
])>{{ $blog->status == 1 ? 'Active' : 'Inactive' }}</span></td>
<td>
<a href="{{ route('blogs.edit', $blog->id) }}" class="btn view btn-block btn-primary btn-xs">{{ __('Edit') }}</a>
<form action="{{ route('blogs.destroy', $blog->id) }}" method="post">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-block btn-danger btn-xs">
{{__('Delete')}}
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center">
{{ __('No Data Found') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- /.card-body -->
<!-- /.card-body -->
<div class="card-footer clearfix">
{{ $blogs->links() }}
</div>
</div>
</div>
</div>
</div>
</section>
@endsection
And now we'll update sidebar
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo -->
<a href="index3.html" class="brand-link">
<img src="{{ asset('assets/backend/img/AdminLTELogo.png') }}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
<span class="brand-text font-weight-light">AdminLTE 3</span>
</a>
<!-- Sidebar -->
<div class="sidebar">
<!-- Sidebar user panel (optional) -->
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
<div class="image">
<img src="{{ asset('assets/backend/img/user2-160x160.jpg')}}" class="img-circle elevation-2" alt="User Image">
</div>
<div class="info">
<a href="#" class="d-block">Alexander Pierce</a>
</div>
</div>
<!-- SidebarSearch Form -->
<div class="form-inline">
<div class="input-group" data-widget="sidebar-search">
<input class="form-control form-control-sidebar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-sidebar">
<i class="fas fa-search fa-fw"></i>
</button>
</div>
</div>
</div>
<!-- Sidebar Menu -->
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
<li class="nav-item">
<a href="{{ route('dashboard') }}" class="nav-link">
<i class="nav-icon fas fa-tachometer-alt"></i>
<p>
Dashboard
</p>
</a>
</li>
<li class="nav-item {{ request()->is('blogs*') ? 'menu-open' : '' }}">
<a href="#" class="nav-link {{ request()->is('blogs*') ? 'active' : '' }}">
<i class="nav-icon fas fa-blog"></i>
<p>
Blog
<i class="right fas fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('blogs.index') }}" class="nav-link {{ request()->is('blogs') || request()->is('blogs/*') ? 'active' : '' }}">
<i class="fa fa-list nav-icon"></i>
<p>All Blogs</p>
</a>
</li>
<li class="nav-item">
<a href="{{ route('blogs.create') }}" class="nav-link {{ request()->is('blogs/create') ? 'active' : '' }}">
<i class="fas fa-blog nav-icon"></i>
<p>Create Blogs</p>
</a>
</li>
</ul>
</li>
</ul>
</nav>
<!-- /.sidebar-menu -->
</div>
<!-- /.sidebar -->
</aside>
Output
And finally, we're ready with our setup. It's time to check our output. Now go to http://127.0.0.1:8000/test/blogs, If everything goes well (hope so) we can see the below output.
Thank you for being so supportive!
0 Comments
CAN FEEDBACK
Emoji