در قسمت دوم آموزش طراحی سایت شخصی با لاراول، ایجاد کردن نمونه کار را بررسی کردیم و حالا میتوانیم نمونه کارهای خودمان را در سایت منتشر کنیم. در این قسمت، اول به دو مبحث تکمیلی میپردازیم و سپس به آموزش ویرایش و حذف نمونه کار (portfolio) در لاراول میپردازیم.
محافظت از Route ها:
اگر آدرس اضافه کردن نمونه کار که به صورت https://127.0.0.1:8000/admin/portfolios/add است را وارد کنید خواهید دید بدون نیاز به لاگین صفحهی اضافه کردن نمونه کار باز خواهد شد. برای اینکه این صفحه و بقیهی صفحات پنل مدیریت نیاز به لاگین داشته باشند باید روی آنها Middleware قرار دهیم. برای آشنایی با Middleware میتوانید به مستندات لاراول مراجعه کنید. Middleware کلاسهایی هستند که قبل از اینکه آدرسی باز شود و کدهای controller آن اجرا شود، اجرا میشود. شما میتوانید به دلخواه با دستور زیر Middleware ایجاد کنید.php artisan make:middleware MiddleWareTest
فایلی با نام MiddleWareTest.php در آدرس app/Http/Middleware ایجاد شده است.
توجه: برای این پروژه نیازی به اجرای دستور بالا ندارید و فقط جهت آموزش است.
برای اینکه تمامی آدرسهای مربوط به پنل مدیریت نیاز به ورود داشته باشند از Middlewareهای آمادهی لاراول استفاده میکنیم.
حال برای محافظت از آدرسهای پنل مدیریت و ایجاد نیاز به ورود برای دسترسی ما از Middlewareهای آمادهی لاراول جهت اینکار استفاده میکنیم.
از Middleware هم در Routeها و هم در controllerها، میتوانیم استفاده کنیم. در اینجا ما بر روی آدرسهای پنل مدیریت که در قسمتهای قبل آنها را گروهبندی کردیم یک Middleware قرار میدهیم.نحوهی قرار دادن Middleware روی یک آدرس به صورت زیر خواهد بود.
Route::middleware('middleware-class-name')->get('/', 'TestController@index')->name('someName');
برای آدرسهای پنل مدیریت که گروهبندی کردهایم نیز به صورت زیر خواهد بود. در اینجا تمامی خصوصیات آدرسها در اولین ورودی تابع group قرار خواهند گرفت.
Route::group(['prefix' => 'admin', 'namespace' => 'Admin', 'middleware'=>'auth'], function () {
Route::get('/dashboard', 'DashboardController@index')->name('admin');
Route::group(['prefix' => 'portfolios'], function (){
Route::get('/', 'PortfolioController@portfolios')->name('viewListPortfolios');
Route::get('/add', 'PortfolioController@view_add')->name('viewAddPortfolio');
Route::post('/add', 'PortfolioController@add')->name('addPortfolio');
});
});
مقدار auth که برای Middleware گروه ادمین انتخاب کردیم، از طرف خود لاراول تعریف شده است که login بودن کاربر را بررسی میکند.
حالا اگر آدرس اضافه کردن نمونه کار را بدون اینکه وارد شده باشید بزنید به صفحهی ورود منتقل خواهید شد.
لیست نمونه کارها در سایدبار:
در قسمتهای قبل ما برای پنل ادمین یک سایدبار قرار دادیم و هدف این بود که بتوانیم به بخشهای مختلف مدیریت دسترسی داشته باشیم. فایل resources/views/admin/layouts/sidebar.blade.php را باز کنید و مقدار زیر را جایگزین کنید.<li><a href="{{ route('admin') }}">Dashboard</a></li>
<li><a href="{{ route('viewListPortfolios') }}">Portfolios</a></li>
حالا با لینکهای موجود در سایت میتوانید بین صفحات لیست نمونه کارها و داشبورد جابجا شوید :)
ویرایش نمونه کار:
در این قسمت از آموزش ویرایش و حذف نمونه کار (portfolio) در لاراول ، ویرایش نمونه کار را آموزش میدهیم. برای ویرایش کردن نمونه کارها نیز مانند اضافه کردن به دو آدرس نیاز داریم. یکی آدرس صفحهی ویرایش است که ID نمونه کار نیز درون آدرس قرار دارد و همچنین آدرسی برای ثبت تغییرات، داریم.ایجاد آدرسها
برای آدرس صفحهی ویرایش نمونه کار باید یک Route به صورت get ایجاد کنیم که این آدرس به متدی از controller متصل خواهد شد که اطلاعات نمونه کار مربوطه را گرفته و به صفحهی view ویرایش نمونه کار، ارسال میکند. برای Route صفحهی ویرایش کد زیر را در گروه نمونه کار در فایل web.php قرار دهید.Route::get('/edit/{portfolio}', 'PortfolioController@view_edit')->name('viewEditPortfolio');
Route::post('/edit/{portfolio}', 'PortfolioController@edit')->name('editPortfolio');
ایجاد فایل view :
برای فایل view در هنگام ویرایش، دقیقا همان فایل اضافه کردن میباشد با این تفاوت که اطلاعات فرم به آدرس دیگری ارسال میشوند و همچنین مقادیر نمونه کار ارسال شده در controller (در مرحلهی بعد خواهیم گفت) نیز در فیلدها باید قرار داده شوند تا ویرایش شوند. فایل view به شکل زیر خواهد بود.@extends('admin.layouts.panel')
@section('content')
<div class="card">
<div class="card-header">Edit Portfolio</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
@if (count($errors))
<div class="alert alert-danger" role="alert">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('editPortfolio', ['portfolio' => $portfolio]) }}" method="post" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" value="{{ $portfolio->title }}" id="title" name="title" aria-describedby="titleHelp">
<small id="titleHelp" class="form-text text-muted">Choose a title for your portfolio</small>
</div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="image" name="image">
<label class="custom-file-label" for="image">Choose image</label>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control" id="description" name="description" rows="3">{{ $portfolio->description }}</textarea>
</div>
<div class="form-group">
<label for="rank">Rank</label>
<select class="custom-select" id="rank" name="rank">
<option value="1" @if($portfolio->rank == 1) {{'selected'}} @endif>1</option>
<option value="2" @if($portfolio->rank == 2) {{'selected'}} @endif>2</option>
<option value="3" @if($portfolio->rank == 3) {{'selected'}} @endif>3</option>
<option value="4" @if($portfolio->rank == 4) {{'selected'}} @endif>4</option>
<option value="5" @if($portfolio->rank == 5) {{'selected'}} @endif>5</option>
</select>
</div>
<div class="form-group">
<label for="client">Client</label>
<input type="text" class="form-control" id="client" value="{{ $portfolio->client }}" name="client">
</div>
<div class="form-group">
<label for="link">Link</label>
<input type="text" class="form-control" id="link" value="{{ $portfolio->link }}" name="link" aria-describedby="linkHelp">
<small id="linkHelp" class="form-text text-muted">Enter project link like:
https://7learn.com</small>
</div>
<div class="form-group">
<label for="completion_date">Completion Date</label>
<input type="text" class="form-control" id="completion_date" value="{{ jdate($portfolio->completion_date)->format('Y/m/d') }}" name="completion_date"
aria-describedby="completion_dateHelp">
<small id="completion_dateHelp" class="form-text text-muted">Attention just enter like:
1398/12/02</small>
</div>
<div class="form-group">
<label for="content">Content</label>
<textarea class="form-control" id="content" name="content" rows="10">{{ $portfolio->content }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
@stop
مقدار متغیر portfolio از سمت controller به فایل view ارسال شده است و ما در این صفحه از آن استفاده کردیم و مقادیر نمونه کار را در فیلدها جهت ویرایش قرار دادیم.
در بالا از تابع route برای آدرس ارسال اطلاعات فرم استفاده کردیم ولی به غیر از نام Route یک ورودی دیگر نیز به آن دادیم. ورودی دوم تابع، یک آرایه میپذیرد که مقادیر پارامترهای Route است. یعنی ما در آدرس ویرایش یک پارامتر به صورت {portfolio} قرار دادیم و در این تابع مقدار این پارامتر را برابر نمونه کار در حال ویرایش آن، قرار دادیم.
توابع controller:
همانطور که قبلتر دو آدرس برای ویرایش نمونه کار ایجاد کردیم حالا برای آن دو آدرس باید دو تابع نیز بنویسیم. تابع اول برای نمایش صفحهی view ویرایش است. controller مربوطه app/Http/Controllers/Admin/PortfolioController.php میباشد.public function view_edit(Portfolio $portfolio)
{
return view('admin.portfolios.edit', ['portfolio' => $portfolio]);
}
در ورودی تابع بالا یک مدل از نمونه کار داریم که در پارامتر آدرس مربوطه نیز نوشته بودیم. حال ممکن است سوالی مطرح شود که ما باید کل مدل اعم از تمامی فیلدها و غیره را در آدرس ارسال کنیم؟ قطعا نه، لاراول در نسخهی جدید خود زمانی که ورودی تابع controller را مدل ارسالی قرار میدهیم به صورت خودکار کلید اصلی (Primary Key) را ملاک برای پارامتر آدرس قرار میدهد که در اینجا id کلید اصلی است.
بعد از ارسال نمونه کار در تابع ما به صورت مستقیم نمونه کار را به صفحهی view ارسال میکنیم.
تابع بعد زمانی است که اطلاعات ویرایش شده و به این تابع جهت ثبت تغییرات در دیتابیس ارسال میشود.
public function edit(Request $request, Portfolio $portfolio)
{
$request->validate([
'title' => 'required',
'description' => 'required',
'rank' => ['required', Rule::in(['1', '2', '3', '4', '5'])],
'client' => 'required',
'link' => 'required',
'completion_date' => 'required',
'content' => 'required',
]);
$portfolio->update([
'title' => $request->title,
'description' => $request->description,
'rank' => $request->rank,
'client' => $request->client,
'link' => $request->link,
'completion_date' => Jalalian::fromFormat('Y/m/d', $request->completion_date)->toCarbon(),
'content' => \request('content'),
]);
if ($request->hasFile('image')){
File::delete(public_path().'/portfolios/'.$portfolio->image);
$image = $request->file('image');
$filename = $portfolio->id . '_' . time() . '.' . $image->getClientOriginalExtension();
$image->move('portfolios', $filename);
$portfolio->image = $filename;
$portfolio->save();
}
session()->put('status', 'Portfolio updated successfully !');
return redirect(route('viewListPortfolios'));
}
در قسمت اول از اعتبارسنجی برای اطلاعات ارسالی استفاده کردیم ولی تصویر را در آن قرار ندادیم زیرا تصویر زمانی عوض میشود که ارسال شود. یعنی اگر شما در صفحهی ویرایش نمونه کار تصویری انتخاب نکنید، همان تصویر برای نمونه کار باقی خواهد ماند.
از تابع update برای ویرایش اطلاعات نمونه کار استفاده کردیم و فقط تصویر را در آن قرار ندادیم.
یک شرط برای زمانی که اگر فایلی با کلید image ارسال شده بود قرار دادیم، به این معنی است که تصویر باید تغییر کند. اول تصویر قبلی نمونه کار را حذف میکنیم و سپس طبق توضیحات قسمت قبل تصویر جدید را قرار میدهیم. حذف کردن فایل تصویر نمونه کار فقط برای این انجام میشود که تصویری اضافی روی سرور نباشد و حجم پروژه بالا نرود.
حال زمانی که آدرس https://127.0.0.1:8000/admin/portfolios/edit/1 را باز کنید باید با صفحهی ویرایش نمونه کار مواجه شوید ولی ما نمیتوانیم که برای هربار ویرایش بخواهیم آدرس را دستی بنویسیم. برای همین باید در لیست نمونه کارها یک دکمه قرار دهیم که به صفحهی ویرایش لینک شود.
کد زیر را برای قسمت جدول در صفحه list.blade.php قرار دهید.
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">id</th>
<th width="20px" scope="col">Image</th>
<th scope="col">Title</th>
<th scope="col">Link</th>
<th scope="col">Client</th>
<th scope="col">Created At</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
@foreach($portfolios as $portfolio)
<tr>
<th scope="row">{{ $portfolio->id }}</th>
<td><img src="{{ asset('portfolios/'.$portfolio->image) }}" alt="Portfolio" class="img-fluid"></td>
<td>{{ $portfolio->title }}</td>
<td>{{ $portfolio->link }}</td>
<td>{{ $portfolio->client }}</td>
<td>{{ jdate($portfolio->created_at)->format('Y/m/d') }}</td>
<td>
<a href="{{ route('viewEditPortfolio', ['portfolio' => $portfolio]) }}" class="btn btn-warning">
<svg class="bi bi-pencil-square" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="https://www.w3.org/2000/svg">
<path d="M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z" clip-rule="evenodd"/>
</svg>
</a>
<form action="#" method="POST" style="display: inline-block">
@method('DELETE')
@csrf
<button type="submit" class="btn-danger btn">
<svg class="bi bi-trash" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="https://www.w3.org/2000/svg">
<path d="M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1 0v6a.5.5 0 001 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 01-1 1H13v9a2 2 0 01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 01-1-1V2a1 1 0 011-1H6a1 1 0 011-1h2a1 1 0 011 1h3.5a1 1 0 011 1v1zM4.118 4L4 4.059V13a1 1 0 001 1h6a1 1 0 001-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" clip-rule="evenodd"/>
</svg>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
حال باید در صفحهی لیست نمونه کارها با صفحه زیر مواجه شوید.
دکمهی زرد رنگ یک تگ لینک برای ویرایش نمونه کار و دکمه قرمز رنگ یک فرم برای حذف نمونه کار که در جلوتر توضیح خواهیم داد.
حذف نمونه کار:
در این قسمت از آموزش ویرایش و حذف نمونه کار (portfolio) در لاراول ، حذف نمونه کار را آموزش میدهیم. برای حذف نمونه کار به یک Route::delete نیاز داریم و سپس یک فرم برای ثبت حذف با متد POST، که بتوانیم اطلاعات را به آدرس مورد نظر ارسال کنیم.ایجاد view:
یک فرم با متد POST ایجاد میکنیم که فقط دو فیلد پنهان، یکی برای تشخیص اینکه متد ارسالی Delete است و دیگری یک فیلد csrf، دارد. ما این کار را در قسمت لیست نمونه کار برای حذف یک نمونه کار ایجاد کردیم. <form action="{{ route('deletePortfolio', ['portfolio' => $portfolio]) }}" method="POST" style="display: inline-block">
@method('DELETE')
@csrf
<button type="submit" class="btn-danger btn">
<svg class="bi bi-trash" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="https://www.w3.org/2000/svg">
<path d="M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1 0v6a.5.5 0 001 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 01-1 1H13v9a2 2 0 01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 01-1-1V2a1 1 0 011-1H6a1 1 0 011-1h2a1 1 0 011 1h3.5a1 1 0 011 1v1zM4.118 4L4 4.059V13a1 1 0 001 1h6a1 1 0 001-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" clip-rule="evenodd"/>
</svg>
</button>
</form>
فرم بالا را در قسمت لیست نمونه کارها قبلتر قرار داده بودیم و شما فقط قسمت action فرم را که یک Route تعریف کردیم (جلوتر توضیح خواهیم داد)، تغییر دهید.
از method('DELETE)@ استفاده کردیم که یک فیلد پنهان به صورت زیر ایجاد میکند.
<input type="hidden" name="_method" value="DELETE">
این فیلد به لاراول میفهماند که متد ارسالی DELETE است و از Route::delete عملیات را ادامه میدهد.
اضافه کردن Route حذف
یک Route با متد Delete باید ایجاد کنیم و آن را به یک تابع از controller مربوطه متصل کنیم. کد زیر را در گروه Routeهای نمونه کار اضافه کنید.Route::delete('/delete/{portfolio}', 'PortfolioController@delete')->name('deletePortfolio');
تابع controller:
در تابع حذف، نمونه کار در ورودی تابع برای ما ارسال میشود و این کار از طریق view که قبلتر توضیح دادیم انجام میشود. برای حذف باید در نظر داشته باشیم که حتما تصویر نمونه کار را حذف کنیم. بعد از آن فقط مدل نمونه کار ارسالی را حذف میکنیم.public function delete(Portfolio $portfolio)
{
File::delete(public_path().'/portfolios/'.$portfolio->image);
$portfolio->delete();
session()->put('status', 'Portfolio deleted successfully!!!');
return redirect(route('viewListPortfolios'));
}
در تابع بالا اول با کمک کلاس File تصویر نمونه کار را در مسیر public/portfolios حذف کردیم و بعد از طریق مدل Portfolio و با تابع delete آن را حذف کردیم.
حالا زمانی که دکمهی حذف را در صفحهی لیست نمونه کارها بزنید باید با تصویر زیر روبرو شوید.
حالا به راحتی میتوانیم نمونه کارهای خود را مدیریت کنیم.
توجه: دقت کنید که اگر در فایل controller خطایی داشتید حتما قسمت اول فایل را بررسی کنید و باید به شکل زیر باشد.
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Portfolio;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Validation\Rule;
use Morilog\Jalali\Jalalian;
این قسمت مربوط به وارد کردن (Import) کلاسهای استفاده شده در کلاس خودمان است که باید به شکل بالا باشد.
تا این لحظه ما تمامی موارد مربوط به نمونه کارها را بررسی کردیم. حال یک پکیج معرفی میکنیم که راحتتر میتوانید با آنها پیغامهای اضافه شدن، ویرایش شدن و حذف یا هر گونه پیغامی پس از انجام یک عملیات را مدیریت کنید.
پکیج Flash Message:
تا این لحظه برای پیغامها ما از session موجود در لاراول استفاده کردیم. زمانی که یک session در controller قرار میدادیم کد آن به شکل زیر بود.session()->put('status', 'Portfolio created successfully !');
در هنگام بازگشت به صفحهی view مورد نظر یک قسمت از کد که به شکل زیر بود اجرا میشد و مقدار status را نمایش میداد.
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
مشکلی که وجود دارد مدت زمان بودن پیغام است و اینکه در تمامی صفحات نیز نشان میدهد. برای مدیریت بهتر ما از یک پکیج به اسم laracasts/flash استفاده میکنیم که راحتتر میتوانید نوع پیغام ( اینکه زرد رنگ یا سبز یا قرمز باشد) را مشخص کنید و همچنین بعد از Refresh دیگر نمایش نخواهد داد.
برای نصب این پکیج دستور زیر را در ترمینال خود اجرا کنید.
composer require laracasts/flash
حالا برای نمایش پیغامها در هر صفحهای که دوست داریم کد زیر را قرار دهید.
@include('flash::message')
کد بالا قسمتی که مربوط به نمایش پیغام در فایلهای view است را وارد (Import) میکند.
حالا در هر قسمت از controller که از sessionاستفاده کردیم، کد زیر را جایگزین کنید.
flash('Portfolio updated successfully !')->success();
میتوانید پیغامهای خودتان را بگذارید و با متد بعد آن که شامل متدهای زیر میباشد میتوانید نوع پیغام و رنگ باکس پیغام را انتخاب کنید.
[lineltr]
- flash('Message')->success()
- flash('Message')->error()
- flash('Message')->warning()
- flash('Message')->important()
- flash('Message')->error()->important() [/lineltr]