در این آموزش قصد داریم پروژه ساخت ثبت نام مهمان ها را با لاراول و Vue.js را انجام بدهیم. دفترچه ثبت نام مهمان ها، مکانی است که در هنگام برگزاری یک مهمانی، مهمان ها مشخصات و نظرات خودشان را در آن می نویسند.
گاها شده است که شما به کافی شاپی مراجعه کردید و یک دفترچه برای ثبت نظر شما وجود دارد. این پروژه باعث می شود به جای استفاده از دفترچه کاغذی یک نرم افزار ساده را روی سیستم و در بستر لوکال پیاده سازی کنید.
پروژه نهایی مطابق شکل زیر است.
در این آموزش همراه با ساخت یک دفتر ثبت نام مهمان ها با برخی از قابلیت ها و امکانات لاراول و Vue.js هم آشنا خواهید شد، از جمله:
ترمینال (در ویندوز Command Prompt) را باز کرده و کد زیر را برای نصب لاراول اجرا کنید.
composer create-project --prefer-dist laravel/laravel guestbook
نکته: معمولاً هنگامی که لاراول را با کامپوزر نصب می کنید به طور خودکار application key در برنامه تنظیم می شود. اما اگر به هر دلیلی تنظیم نشد، با پیغام خطای No application encryption key has been specified و یا “ The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key length” در هنگام اجرای لاراول مواجه خواهید شد.
برای رفع این مشکل دستور زیر را در ترمینال وارد کنید.
php artisan key:generate
در صورتی که همه کارها را به درستی انجام داده باشید، با تایپ دستور php artisan serve در ترمینال باید برنامه به درستی اجرا شود.
پیکربندی پایگاه های داده که لاراول از آن استفاده می کند در فایل .env قرار دارد.
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret
در این آموزش من از پایگاه داده sqlite استفاده می کنم. پس در قسمت DB_Connection عبارت sqlite را می نویسم و بقیه کدها را هم پاک می کنم
DB_CONNCTION=SQLite
در صورتی که DB_DATABASE را پاک کنید، لاراول فرض می کند شما با پایگاه داده ای که در مسیر database/database.sqlite قرار دارد، کار می کنید. برای ساخت این پایگاه داده، کد زیر را در ترمینال وارد کنید.
touch database/database.sqlite
در این پروژه ما فقط به یک مدل و Migration نیاز داریم و نام این مدل را هم Signature می گذاریم. برای ایجاد مدل به همراه Migration دستور زیر را در ترمینال وارد کنید.
php artisan make:model Signature -m
هنگامی که از –m در دستور php artisan make:model استفاده می کنید، یک Migration هم برای تان ایجاد خواهد شد. با انجام این کار در زمان و حجم کار صرفه جویی خواهد شد.
فایل Migrationیی که ایجاد شد مطابق زیر است:
class CreateSignaturesTable extends Migration { /** **_ Run the migrations. _** **_ @return void _**/ public function up() { Schema::create('signatures', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email'); $table->text('body'); $table->timestamp('flagged_at')->nullable(); $table->timestamps(); }); } /** _ Reverse the migrations. _ _ @return void _/ public function down() { Schema::dropIfExists('signatures'); } }
از نام ستون ها می توانید بفهمید که هر کدام برای چه منظوری استفاده می شود. اما شاید یک سوال برای شما پیش بیاید که ستون flagged_at چیست؟
نوع این ستون timestamp است و هنگامی که یک رکوردی را در پایگاه داده درج کنید، تاریخ و زمان درج این رکورد در ستون flagged_at ذخیره خواهد شد.
فایل migration تان را ذخیره کرده و دستور زیر را در ترمینال برای اجرای این migration اجرا کنید.
php artisan migrate
و در انتها فایل مدل را باز کرده و کدهای زیر را به این مدل اضافه کنید. برای جلوگیری از بروز خطای mass assignment، باید فیلدهایی که در زیر مشخص شده اند را به آرایه $fillable اضافه کنیم.
/_* _ Field to be mass-assigned. _ _ @var array */ protected $fillable = ['name', 'email', 'body', 'flagged_at'];
در صورتی که نمی دانید خطای mass assignment چیست، لطفاً پاراگراف زیر را بخوانید:
خطا یا آسیب پذیری mass assignment هنگامی رخ می دهد که کاربر یک پارامتر HTTP پیش بینی نشده را از طریق یک درخواست ارسال کند و آن پارامتر یک ستون پایگاه داده را بطور ناخواسته تغییر دهد. برای مثال یک هکر ممکن است با ارسال پارامتر is_admin از طریق یک درخواست HTTP که به داخل متدی که در مدل تعریف کرده اید، ارسال شود و به هکر اجازه می دهد که خودش را به عنوان مدیر سیستم (Administrator) جا بزند و کنترل کامل برنامه را بدست بگیرد.
در قدم بعدی باید یک سری داده های ساختگی ایجاد کنیم. برای اینکار از Model Factory ها استفاده می کنیم. خوشبختانه با انتشار لاراول 5.7 یک روش آسان برای ذخیره این Factoryها ایجاد شده است و ما می توانیم هر factory را داخل فایل مخصوص به خود آن قرار دهیم. برای ساخت Factory، ابتدا ترمینال را باز کرده و دستور زیر را در آن اجرا کنید:
php artisan make:factory SignatureFactory
و برای ساخت داده های ساختگی از Faker استفاده می کنیم. Faker یک کتابخانه php است که برای تولید داده های ساختگی بر اساس ستون های جدول پایگاه داده استفاده می شود.
$factory->define(App\Signature::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->safeEmail, 'body' => $faker->sentence ]; });
حال model factory مان آماده است. اکنون می توانیم داده های ساختگی مان را تولید کنیم. برای اینکار ترمینال را باز کرده و دستور زیر را در آن اجرا کنید.
php artisan tinker
سپس دستور زیر را در ترمینال اجرا کنید.
factory(App\Signature::class, 100)->create();
در مثال بالا ما 100 رکورد ایجاد کردیم، اما شما می توانید به هر میزان که تمایل داشتید رکوردهایی را ایجاد کنید و برای اینکار کافی است تعداد رکوردهای مورد نظرمان را با عدد 100 جایگزین کنیم.
تعریف روت
ابتدا یک resource ایجاد کرده و روت های زیر را به آن اضافه کنید.
فایل route->api.php را باز کنید و کد زیر را در آن قرار دهید.
Route::resource('signatures', 'Api\SignatureController') ->only(['index', 'store', 'show']);
Routes -> api.php:
Route::put('signatures/{signature}/report', 'Api\ReportSignature@update');
ایجاد کنترلرها
همان طور که قبلاً در قسمت روت ها دیدید، نیاز به کنترلرهای SignatureController و ReportSignature داریم.
برای ایجاد کنترلر SignatureController دستور زیر را در ترمینال اجرا کنید:
php artisan make:controller Api/SignatureController
و کدهای زیر را در آن قرار دهید:
<?php namespace App\Http\Controllers\Api; use App\Signature; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Http\Resources\SignatureResource; class SignatureController extends Controller { /** **_ Return a paginated list of signatures. _** **_ @return SignatureResource _**/ public function index() { $signatures = Signature::latest() ->ignoreFlagged() ->paginate(20); return SignatureResource::collection($signatures); } /** _ Fetch and return the signature. _ _ @param Signature $signature _ @return SignatureResource _/ public function show(Signature $signature) { return new SignatureResource($signature); } /** _ Validate and save a new signature to the database. _ _ @param Request $request _ @return SignatureResource _/ public function store(Request $request) { $signature = $this->validate($request, [ 'name' => 'required|min:3|max:50', 'email' => 'required|email', 'body' => 'required|min:3' ]); $signature = Signature::create($signature); return new SignatureResource($signature); } }
همان طور که در متد index می بینید، ما از متدی به نام ignoreFlagged() استفاده کردیم. این متد تنها رکوردهایی که flag نشده باشند را بر می گرداند (یعنی مقدار ستون flagged_at برابر null باشد).
شما باید این متد را داخل مدل Signature تعریف کنید، مطابق زیر:
/_* _ Ignore flagged signatures. _ _ @param $query _ @return mixed _/ public function scopeIgnoreFlagged($query) { return $query->where('flagged_at', null); }
حال باید کنترلر ReportSignature را ایجاد کنیم
php artisan make:controller Api/ReportSignature
و کدهای زیر را داخل این کنترلر قرار دهید:
<?php namespace App\Http\Controllers\Api; use App\Signature; use App\Http\Controllers\Controller; class ReportSignature extends Controller { /_* _ Flag the given signature. _ _ @param Signature $signature _ @return Signature _/ public function update(Signature $signature) { $signature->flag(); return $signature; } }
هنگام بازیابی، رکوردها توسط ویژگی Model Binding یک متد به نام flag() را روی ستون های flagged_at که حاوی تاریخ و زمان جاری است را فراخوانی می کنیم.
شما می توانید این قابلیت را با تعریف این متد در مدل Signatureتان اضافه کنید.
/_* _ Flag the given signature. _ _ @return bool */ public function flag() { return $this->update(['flagged_at' => \Carbon\Carbon::now()]); }
از لاراول 5.5 به بعد امکانات خیلی خوبی به این فریم ورک اضافه شد. در صورتی که قبلاً با ساخت API در لاراول کار کرده باشید احتمالاً می دانید که همیشه نیاز به تبدیل کننده ها دارید، چون نباید کاربر، ساختار جداول پایگاه داده تان را بطور مستقیم مشاهده کند، چون از نظر امنیتی کار درستی نیست.
در مثال ما تنها به یک تبدیل کننده رکوردها نیاز داریم. برای ایجاد آن ترمینال را باز کرده و دستور زیر را در آن اجرا کنید:
php artisan make:resource SignatureResource
و کدهای زیر را در آن قرار دهید:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\Resource; class SignatureResource extends Resource { /_* _ Transform the resource into an array. _ _ @param \Illuminate\Http\Request _ @return array _/ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'avatar' => $this->avatar, 'body' => $this->body, 'date' => $this->created_at->diffForHumans() ]; } }
چون ما یک پروپرتی avatar فراخوانی می کنیم که قبلا آن را ایجاد نکرده بودیم، باید یک accessor برای آن بنویسیم.
استفاده از این داخل مدل Signature خیلی کار خوبی است، چون ما نمی خواهیم آدرس ایمیل مهمان ها را نمایش دهیم
/_* _ Get the user Gravatar by their email address. _ _ @return string */ public function getAvatarAttribute() { return sprintf('https://www.gravatar.com/avatar/%s?s=100', md5($this->email)); }
بعد از اینکه endpoint، کنترلر و تبدیل کننده هایتان را ایجاد کردید، باید آنها را تست کنید تا مطمئن شوید که همه چیز به درستی کار می کند.
برای تست endpointها از برنامه postman استفاده می کنیم. برای دانلود این برنامه به سایت رسمی آن بروید و نسخه مطابق با سیستم عاملتان را دانلود کنید.
من تست هایی که انجام می دهم را در یک کالکشن به نام scout Guestbook ذخیره می کنم.
بعد از اینکه تست تان را ایجاد کردید، روی دکمه save کلیک کنید تا تست تان ذخیره شود.
برای ایجاد کالکشن، از منوی File -> New… را انتخاب کنید. سپس از پنجره باز شده collection را انتخاب و در قسمت Name یک نام برای آن انتخاب کنید.
تنظیم بخش front-end
ما بخش backend برنامه را ساختیم، حال باید این بخش را به frontend متصل کنیم.
تنظیم صفحه خانگی
Get: / - این آدرس نقطه ورودی برنامه مان است و صفحه خانگی پروژه را نشان می دهد.
routes ->web.php:
Route::get('/', 'SignaturesController@index')->name('home');
سپس مطابق زیر کنترلر SignatureController را ایجاد کنید.
php artisan make:controller SignaturesController
و کدهای زیر را در کنترلر ایجاد شده بنویسید
<?php namespace App\Http\Controllers; class SignaturesController extends Controller { /_* _ Display the GuestBook homepage. _ _ @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function index() { return view('signatures.index'); } }
یک فایل با نام master.blade.php ایجاد کنید. در این فایل قالب اصلی برنامه را طراحی می کنیم و پس از آن هر صفحه که در برنامه ایجاد می کنیم باید از این فایل به عنوان قالب اصلی خود استفاده کند.
کدهای زیر را در فایل master.blade.php قرار دهید.
<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Scotch.io GuestBook</title> <meta name="csrf-token" content="{{ csrf_token() }}"> <link href="{{ mix('css/app.css') }}" rel="stylesheet" type="text/css"> </head> <body> <div id="app"> <nav class="navbar navbar-findcond"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="{{ route('home') }}">GuestBook</a> </div> <div class="collapse navbar-collapse" id="navbar"> <ul class="nav navbar-nav navbar-right"> <li class="active"> <a href="{{ route('sign') }}">Sign the GuestBook</a> </li> </ul> </div> </div> </nav> @yield('content') </div> <script src="{{ mix('js/app.js') }}"></script> </body> </html>
حال یک ویوی جدید با نام index.blade.php داخل فولدر Signature ایجاد کنید و کدهای زیر را در آن قرار دهید.
@extends('master') @section('content') <div class="container"> <div class="row"> <div class="col-md-12"> <signatures></signatures> </div> </div> </div> @endsection
Get: /sign – در این صفحه یک فرمی را نمایش می دهیم تا کاربر با پر کردن آن یک رکورد را در پایگاه داده درج کند.
Route::get('sign', 'SignaturesController@create')->name('sign');
ما قبلاً کنترلری برای اینکار ایجاد کرده بودیم (SignatureController).
کدهای زیر را در این کنترلر قرار دهید.
/_* _ Display the GuestBook form page. _ _ @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function create() { return view('signatures.sign'); }
سپس یک ویو با نام Sign.blade.php در مسیر resource -> Views ->Signature ایجاد کرده و کدهای زیر را در آن قرار دهید:
@extends('master') @section('content') <div class="container"> <div class="row"> <div class="col-md-12"> <signature-form></signature-form> </div> </div> </div> @endsection
قبل از لاراول 5.5 امکانی فراهم شد که کاربران می توانستند از کتابخانه های بوت استرپ و Vue.js داخل فریم ورک استفاده کنند. بعضی از کاربران تمایل داشتند که از کتابخانه های دیگری هم استفاده کنند. از لاراول 5.5 به بعد این امکان ایجاد شد که از سایر فریمورک های جاوا اسکریپت هم بتوانید در لاراول استفاده کنید. برای مثال با دستور زیر می توانید فریمورک react را با لاراول اضافه کنید.
php artisan preset react
و با دستور زیر می توانید فقط بوت استرپ را نصب کنید . از نصب سایر فریمورک های جاوا اسکریپت صرف نظر کنید.
php artisan preset bootstrap
و یا در صورتی که تمایل نداشته باشید از هیچ فریمورک یا کتابخانه ای استفاده کنید، دستور زیر را در ترمینال وارد کنید:
php artisan preset none
در این مثال ما از همان کتابخانه های بوت استرپ و Vue.js استفاده می کنیم. حال برای نصب وابستگی های جاوا اسکریپت، دستور زیر را در ترمینال وارد کنید.
npm install
فایل resources -> assets -> sass -> app.scss را باز کرده و کدهای زیر را برای استایل دهی به برنامه اضافه کنید
$color_1: #f14444; $color_2: #444; $color_3: #fff; $border_color_1: #ccc; $border_color_2: #fff; $border_color_3: #f14444; nav.navbar-findcond { background: #fff; border-color: $border_color_1; box-shadow: 0 0 2px 0 #ccc; a { color: $color_1; } ul.navbar-nav { a { color: $color_1; border-style: solid; border-width: 0 0 2px 0; border-color: $border_color_2; &:hover { background: #fff; border-color: $border_color_3; } &:visited { background: #fff; } &:focus { background: #fff; } &:active { background: #fff; } } } ul.dropdown-menu { >li { >a { color: $color_2; &:hover { background: #f14444; color: $color_3; } } } } } button[type="submit"] { border-radius: 2px; color: $color_3; background: #e74c3c; padding: 10px; font-size: 13px; text-transform: uppercase; margin: 0; font-weight: 400; text-align: center; border: none; cursor: pointer; width: 10%; transition: background .5s; &:hover { background: #2f3c4e; } }
سپس دستور زیر را در ترمینال وارد کنید
npm run dev
در این مرحله تنها کاری که باقی مانده است تا بتوانیم برنامه را تکمیل کنیم، ایجاد کامپوننتی است که در زیر مشخص کردیم:
<signatures></signatures> <!-- In index.blade.php --> <signature-form></signature-form> <!-- In sign.blade.php -->
برای انجام این کار دو فایل زیر را در مسیر resource -> assets -> components -> js ایجاد کنید و کدهای زیر را در آن قرار دهید.
فایل Signature.vue
<template> <div> // Our HTML template </div> </template> <script> export default { // Our Javascript logic } </script>
فایل SignatureForm.vue
<template> <div> // Our HTML template </div> </template> <script> export default { // Our Javascript logic } </script>
حال برای اینکه برنامه مان این فایل ها را بشناسد، باید آنها را ثبت کنیم. برای اینکار فایل resources -> assets -> app.js را باز کرده و کدهای زیر را در آن قرار دهید.
Vue.component('signatures', require('./components/Signatures.vue')); Vue.component('signature-form', require('./components/SignatureForm.vue')); const app = new Vue({ el: '#app' });
برای اینکه بتوانیم همه رکوردها را به صورت صفحه بندی شده مشاهده کنیم، من از پکیج vuejs-paginate استفاده می کنم. ترمینال را باز کرده و دستور زیر را برای نصب این پکیج را اجرا کنید.
npm install vuejs-paginate --save
سپس آن را در فایل resources ->assets ->app.js ثبت کنید.
Vue.component('paginate', require('vuejs-paginate'));
کدهای زیر را داخل کامپوننت Signatures.vue قرار دهید:
<template> <div> <div class="panel panel-default" v-for="signature in signatures"> <div class="panel-heading"> <span class="glyphicon glyphicon-user" id="start"></span> <label id="started">By</label> {{ signature.name }} </div> <div class="panel-body"> <div class="col-md-2"> <div class="thumbnail"> <img :src="signature.avatar" :alt="signature.name"> </div> </div> <p>{{ signature.body }}</p> </div> <div class="panel-footer"> <span class="glyphicon glyphicon-calendar" id="visit"></span> {{ signature.date }} | <span class="glyphicon glyphicon-flag" id="comment"></span> <a href="#" id="comments" @click="report(signature.id)">Report</a> </div> </div> <paginate :page-count="pageCount" :click-handler="fetch" :prev-text="'Prev'" :next-text="'Next'" :container-class="'pagination'"> </paginate> </div> </template> <script> export default { data() { return { signatures: [], pageCount: 1, endpoint: 'api/signatures?page=' }; }, created() { this.fetch(); }, methods: { fetch(page = 1) { axios.get(this.endpoint + page) .then(({data}) => { this.signatures = data.data; this.pageCount = data.meta.last_page; }); }, report(id) { if(confirm('Are you sure you want to report this signature?')) { axios.put('api/signatures/'+id+'/report') .then(response => this.removeSignature(id)); } }, removeSignature(id) { this.signatures = _.remove(this.signatures, function (signature) { return signature.id !== id; }); } } } </script>
همان طور که در بالا می بینید وقتی که کامپوننت ایجاد شد، متد fetch() را برای ایجاد یک درخواست Get به آدرس endpoint یی که در data object تعریف کردیم، فراخوانی می کنیم. سپس مقادیر برگشت داده شده از API را داخل آرایه Signature می ریزیم.
سپس داخل کدهای Html در میان آرایه Signature پیمایش کرده و تمام آنها را نمایش می دهیم.
هنگامی که کاربر روی لینک report کلیک کرد، متد report را فراخوانی می کنیم. این متد id یک رکورد مشخص را به عنوان پارامتر دریافت کرده و یک درخواست put برای پنهان کردن رکوردهای گزارش گیری شده ایجاد می کند و removeSignature هم وظیفه حذف عناصر آرایه را بر عهده دارد.
ما در کامپوننت SigntatureForm فرم برنامه را ایجاد کردیم و همه input های این فرم را به data object متصل کردیم. هنگامی که مهمان فرم را پر کرد و روی دکمه submit کلیک کند، ما یک درخواست post را برای ذخیره یک رکورد جدید اجرا می کنیم.
اگر همه چیز با موفقیت انجام شد مقدار پروپرتی saved را به true تغییر می دهیم و فرم را reset می کنیم و اگر خطایی رخ داد، مقدار پروپرتی errors را نمایش می دهیم.
<template> <div> <div class="alert alert-success" v-if="saved"> <strong>Success!</strong> Your signature has been saved successfully. </div> <div class="well well-sm" id="signature-form"> <form class="form-horizontal" method="post" @submit.prevent="onSubmit"> <fieldset> <legend class="text-center">Sign the GuestBook</legend> <div class="form-group"> <label class="col-md-3 control-label" for="name">Name</label> <div class="col-md-9" :class="{'has-error': errors.name}"> <input id="name" v-model="signature.name" type="text" placeholder="Your name" class="form-control"> <span v-if="errors.name" class="help-block text-danger">{{ errors.name[0] }}</span> </div> </div> <div class="form-group"> <label class="col-md-3 control-label" for="email">Your E-mail</label> <div class="col-md-9" :class="{'has-error': errors.email}"> <input id="email" v-model="signature.email" type="text" placeholder="Your email" class="form-control"> <span v-if="errors.email" class="help-block text-danger">{{ errors.email[0] }}</span> </div> </div> <div class="form-group"> <label class="col-md-3 control-label" for="body">Your message</label> <div class="col-md-9" :class="{'has-error': errors.body}"> <textarea class="form-control" id="body" v-model="signature.body" placeholder="Please enter your message here..." rows="5"></textarea> <span v-if="errors.body" class="help-block text-danger">{{ errors.body[0] }}</span> </div> </div> <div class="form-group"> <div class="col-md-12 text-right"> <button type="submit" class="btn btn-primary btn-lg">Submit</button> </div> </div> </fieldset> </form> </div> </div> </template> <script> export default { data() { return { errors: [], saved: false, signature: { name: null, email: null, body: null, } }; }, methods: { onSubmit() { this.saved = false; axios.post('api/signatures', this.signature) .then(({data}) => this.setSuccessMessage()) .catch(({response}) => this.setErrors(response)); }, setErrors(response) { this.errors = response.data.errors; }, setSuccessMessage() { this.reset(); this.saved = true; }, reset() { this.errors = []; this.signature = {name: null, email: null, body: null}; } } } </script>
بعد از ایجاد این کامپوننت و یا تغییر در آنها، حتما دستور زیر را برای کامپایل آنها اجرا کنید.
npm run dev
بسیار عالی. به شما تبریک می گوییم. با این آموزش توانستید یک دفترچه ثبت نظرات مشتریان و مهمان ها را به صورت هوشمند استفاده کنید. این ایده می تواند در کافی شاپ ها، رستوران ها، مراکز تفریحی و فرهنگی جایگزین دفترچه های کاغذی شود. ممنون از همراهی شما.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.