با Laravel و Vue یک برنامه وب مدرن بسازید - قسمت 2: ایجاد endpoints با در نظر گرفتن REST

در آموزش قبلی از سری آموزش های لاراول، نحوه ایجاد موفقیت آمیز یک محیط توسعه را آماده کردیم که ما را برای توسعه PHP آماده می کند. از طریق بقیه بخش ها، یک "Trello Clone" ساده خواهیم ساخت.

در این آموزش لاراول، بررسی می کنیم که چگونه تنظیم برنامه "RESTully" در ساخت برنامه های مدرن با استفاده از Vue و Laravel نقش دارد.

در پایان این آموزش، آنچه که ما خواهیم داشت به شرح زیر خواهد بود:

App Demo

پیش نیازها

این قسمت از آموزش مستلزم داشتن موارد زیر است:

  • قسمت اول این سری از آموزشهای لاراول را کامل کنید و development environment خود را به درستی تنظیم کنید.

ایجاد برنامه

برای شروع، ما باید برنامه خود را ایجاد کنیم. از آنجایی که ما در بخش اول محیط توسعه خود را تنظیم کرده ایم، تمام آنچه را که لازم داریم اجرای دستور زیر برای ایجاد یک برنامه جدید لاراول است:

    $ laravel new trello-clone

برای اجرای تست برنامه خود، با cd به دایرکتوری trello-clone بروید و سپس دستور را اجرا کنید:

    $ php artisan serve

برنامه لاراول شما را روی  127.0.0.1:8000 اجرا می کند. برای از kill کردن  سرور، ctrl+c را بر روی ماشین خود فشار دهید. اگر Laravel valet را روی ماشین خود نصب و پیکربندی کرده اید، می توانید با cd به دایرکتوری trello-clone رفته و دستور زیر را اجرا کنید:

    $ valet link trello-clone

سپس به آدرس http://trello-clone.test بروید. همانطور که در تصویر زیر مشخص است، باید صفحه خوش آمدگویی لاراول را مشاهده کنید:

laravel homepage

ساخت model ها

برای trello clone ساده خود، مدل های زیر را خواهیم داشت:

  • User.
  • Task.
  • Category.

برای ساخت یک مدل، دستورات زیر را اجرا کنید. از آنجا که ما در حال حاضر مدل User را داریم، به منابع  Task و Category نیاز داریم. اکنون می توانیم مدل ها را ایجاد کنیم.

    $ php artisan make:model ModelName -mr

فلگ -mr یک فایل migration و resource controller برای مدل ایجاد می کند.

User model

لاراول با یک User model پیش فرض همراه است بنابراین نیازی به ایجاد آن ندارید. User model دارای فیلدهای زیر است:

  • id - شناسه منحصر به فرد auto-incrementing کاربر است.
  • name - نام کاربر است.
  • email - ایمیل کاربر.
  • password - در احراز هویت استفاده می شود.

User model را که در دایرکتوری app است باز کنید و آن را به صورت زیر آپدیت کنید:

    hasMany(Task::class);
        }
    }

SoftDeletes روشی است برای حذف resource ها بدون حذف واقعی داده ها از دیتابیس. اتفاقی که می افتد این است که وقتی تیبل ایجاد می شود، فیلدی به نام 'deleted_at' به وجود می آید و هنگامی که کاربر سعی در حذف تسکی دارد، فیلد 'deleted_at' با زمان فعلی جمع می شود. بنابراین، وقتی واکشی برای resource ها انجام می شود، resource "حذف شده" بخشی از پاسخ نخواهد بود

Task model

Task model دارای فیلدهای زیر است:

  • id – یک unique id برای تسک.
  • name – نام تسک.
  • category_id – شناسه دسته ای که تسک به آن تعلق دارد.
  • user_id – شناسه کاربری که تسک به آن تعلق دارد.
  • order – ترتیب تسک در رده مربوطه.

با استفاده از دستور artisan یک Task model ایجاد کنید. سپس آن را از دایرکتوری app باز کنید و کد زیر را در آن وارد کنید:

    hasOne(Category::class);
        }

        public function user() {
            return $this->belongsTo(User::class);
        }
    }

Category model

category model دارای فیلدهای زیر است:

  • id – به طور منحصر به فرد هر category را شناسایی می کند.
  • name – نشان دهنده نام category است.

با استفاده از دستور artisan یک Category model ایجاد کنید. سپس آن را از دایرکتوری app باز کنید و محتوای آن را با موارد زیر جایگزین کنید:

    hasMany(Task::class);
        }
    }

در اینجا، تابع tasks() کمک می کند به تعریف روابط بین Category model و Task model که به عنوان یک رابطه one-to-many است. به این معنی که یک category دارای task های زیادی است.

Writing our migrations

برای کار با این برنامه، باید دیتابیس ایجاد کنیم. برای پیگیری تغییرات در دیتابیس خود، از migration ها استفاده می کنیم که یکی از فیچرهای داخلی لاراول است.

به عنوان بخشی از پیش نیازهای ذکر شده در قسمت اول این سری، شما نیاز به نصب SQLite بر روی ماشین خود دارید. ایجاد یک دیتابیس SQLite که لاراول می تواند با اتصال آن یک فایل جدید خالی در دایرکتوری database به نام database.sqlite ایجاد کند.

سپس، فایل .env را در روت پروژه باز کرده و خطوط زیر را جایگزین کنید:

    DB_CONNECTION=mysql
    DB_DATABASE=homestead
    DB_USERNAME=username
    DB_PASSWORD=password

با

    DB_CONNECTION=sqlite
    DB_DATABASE=/full/path/to/database/database.sqlite

این برای راه اندازی دیتابیس ماست. در فایل .env، مقدار APP_URL را از http://localhost به http://127.0.0.1:8000 تغییر دهید زیرا این آدرس URL برنامه خواهد بود.

دستور ایجاد  migration این است:

    $ php artisan make:migration create_tablename_table

شما می توانید فایل migration خود را هرچه دوست دارید نامگذاری کنید، اما همیشه نامگذاری آن مانند verb_tablename_table خوب است همانطور که در بالا نشان داده شده است. فایل در دایرکتوری database/migrations ایجاد خواهد شد.

از آنجایی که ما قبلاً هنگام ایجاد model های خود از فلگ -mr استفاده کرده ایم، باید migration ها برای ما ایجاد شده باشد.

Migration ها مبتنی بر زمان اجرا یا همان runtime-based هستند. بنابراین هنگام Migration برای table های وابسته به یکدیگر، باید این مورد را در نظر بگیرید.

Updating our user migration

فایل create users migration را در دایرکتوری database/migrations باز کرده و کد زیر را جایگزین کنید:

    increments('id');
                $table->string('name');
                $table->string('email')->unique();
                $table->string('password');
                $table->rememberToken();
                $table->timestamps();
                $table->softDeletes();
            });
        }

        public function down()
        {
            Schema::dropIfExists('users');
        }
    }

Updating our category migration

از آنجا که قبلاً category را ایجاد کرده بودیم، فایل را باز کرده و کد زیر را جایگزین کنید:

    increments('id');
                $table->string('name');
                $table->timestamps();
                $table->softDeletes();
            });
        }

        public function down()
        {
            Schema::dropIfExists('categories');
        }
    }

Creating our task migration

از آنجا که فایل task migration را قبلا ایجاد کردیم، فایل را باز کنید و کد زیر را جایگزین کنید:

    increments('id');
                $table->string('name');
                $table->unsignedInteger('category_id');
                $table->unsignedInteger('user_id');
                $table->integer('order');
                $table->timestamps();
                $table->softDeletes();

                $table->foreign('user_id')->references('id')->on('users');
                $table->foreign('category_id')->references('id')->on('categories');
            });
        }

        public function down()
        {
            Schema::dropIfExists('tasks');
        }
    }

اکنون که فایل های migration خود را داریم، بیایید دستور artisan را برای اجرای migration ها اجرا کنیم و در دیتابیس بنویسیم:

    $ php artisan migrate

Migration ها مانند version control برای دیتابیس شما هستند. این به شما امکان می دهد بدون نیاز به نوشتن دستی کوئری های SQL، دایتابیس خود را ایجاد، اصلاح یا از بین ببرید.

Database seeders

اکنون که migration های دیتابیس خود را ایجاد کردیم، بیایید ببینیم چگونه dummy data را برای زمانی که برنامه های خود را آزمایش می کنیم، قرار دهیم. در لاراول، چیزی به نام seeder داریم.

 Seeder ها به شما امکان می دهند dummy data را به طور خودکار در دیتابیس وارد کنید.

این دستور ساخت یک seeder است:

    $ php artisan make:seeder TableNameSeeder

ایجاد users table seeder

برای ایجاد database seeder دستور زیر را تایپ کنید:

    $ php artisan make:seeder UsersTableSeeder

این یک فایل UsersTableSeeder.php در دایرکتوری database/seeds ایجاد می کند. فایل را باز کنید و محتوای آن را با کد زیر جایگزین کنید:

     'John Doe',
                'email' => 'demo@demo.com',
                'password' => bcrypt('secret'),
            ]);
        }
    }

تابع run شامل کوئری دیتابیس است که می خواهیم هنگام اجرای seeder ها run شود.

برای ایجاد seed data بهتر می توانید از model factory ها استفاده کنید.

ما قبل از ذخیره پسورد از bcrypt برای هش کردن رمز عبور استفاده می کنیم زیرا این الگوریتم hash پیش فرض است که لاراول از آن برای hash password ها استفاده می کند.

ایجاد categories table seeder

برای ایجاد database seeder دستور زیر را تایپ کنید:

    $ php artisan make:seeder CategoriesTableSeeder

این یک فایل CategoriesTableSeeder.php در دایرکتوری database/seeds ایجاد می کند. فایل را باز کنید و محتوای آن را با کد زیر جایگزین کنید:

     $category]);
            }
        }
    }

اجرای database seeders

برای اجرای database seeder ها، فایل database/DatabaseSeeder.php را باز کنید و کد زیر را جایگزین متد run کنید:

    public function run()
    {
        $this->call([
            UsersTableSeeder::class,
            CategoriesTableSeeder::class,
        ]);
    }

بعد، دستور زیر را روی ترمینال خود اجرا کنید:

    $ php artisan db:seed

این باید دیتابیس را با داده به روز کند. هر زمان می خواهید داده های خود را refresh و seed کنید، دستور زیر را اجرا کنید:

    $ php artisan migrate:fresh --seed

با این کار جداول دیتابیس حذف می شوند، آنها را دوباره اضافه کنید و seeder را اجرا می کنید.

REST در nutshell

از نظر فنی، REST مخفف REpresentational State Transfer است. برای اینکه درک خوبی از این مقاله داشته باشید، دو اصطلاح وجود دارد.

Clients، statelessness، resources و relationship چیست؟

کلاینت ها دستگاه هایی هستند که با برنامه شما ارتباط برقرار می کنند. برای هر برنامه خاص، تعداد کلاینت هایی که با آن ارتباط برقرار می کنند می توانند از یک تا میلیارد نفر باشند. وقتی به وب سایتی می روید (به عنوان مثال https://pusher.com) کلاینت شما درخواستی را به سرور ارسال می کند. سپس سرور درخواست شما را پردازش کرده و سپس پاسخی را برای تعامل با client ارسال می کند.

Statelessness در ساده ترین اصطلاح به معنای ساختن برنامه به گونه ای است که client تمام نیازهایش را برای تکمیل هر درخواست در اختیار دارد. هنگامی که کلاینت درخواست های بعدی را ارسال می کند، سرور داده های مربوط به کلاینت را ذخیره یا بازیابی نمی کند. هنگامی که برنامه شما دارای کاربران concurrent فعال بیشتری است، یک بار غیر ضروری برای مدیریت سرور شما برای کلاینت خواهد بود. stateless بودن، طراحی برنامه شما را نیز ساده می کند.

Resource ها نمایانگر موارد واقعی در کد شما هستند. به عنوان مثال در حال ساخت برنامه ای هستید که به دانش آموزان امکان می دهد نمرات خود را بررسی کنند، مثال خوبی از Resource در چنین برنامه ای students، courses ها و غیره است. این منابع با داده هایی که در دیتابیس ذخیره می شوند مرتبط هستند.

اکنون وقتی در حال ساخت برنامه های RESTful هستیم، سرور ما به client امکان دسترسی به resource ها را می دهد. سپس کلاینت می تواند درخواست هایی را برای واکشی، تغییر یا حذف resource ها ایجاد کند. منابع معمولاً در فرمت های JSON یا XML نشان داده می شوند اما فرمت های بسیار بیشتری وجود دارد و تصمیم گیری در مورد فرمت به شما بستگی دارد.

ایجاد چند REST endpoints

در حال حاضر HTTP Verb های زیر را داریم که می خواهیم آنها را اعمال کنیم:

  • GET - معمولاً برای واکشی یک resource استفاده می شود
  • POST - این برای ایجاد یک resource جدید استفاده می شود
  • PUT/PATCH- برای replace/update منبع موجود استفاده می شود
  • DELETE - برای حذف یک resource استفاده می شود

در اینجا یک جدول ارائه شده است که نشان می دهد REST endpoint ها برای resource چگونه به نظر می رسد:

METHOD ROUTE FUNCTION
POST /api/task Creates a new task
GET /api/task Fetches all tasks
GET /api/task/{task_id} Fetches a specific task
PUT PATCH /api/task/{task_id} | Update a specific task
DELETE /api/task/{task_id} Delete a specific task

بیایید شروع به ایجاد route ها در برنامه خود کنیم. فایل routes/api.php را باز کنید و آپدیت کنید:

    <‌?php

    Route::resource('/task', 'TaskController');
    Route::get('/category/{category}/tasks', 'CategoryController@tasks');
    Route::resource('/category', 'CategoryController');

در بالا، route های خود را مشخص کردیم. ما دو route resource داریم که همه route های دیگر را بدون نیاز به ایجاد دستی برای ما ثبت می کنند.

فرمت response ها و مدیریت خطاهای API

در اوایل مقاله، ما در مورد درخواست از سمت client صحبت کردیم. حال بیایید نحوه ایجاد و فرمت response را بررسی کنیم.

ایجاد controller ها

اکنون که route های خود را پیدا کرده ایم، باید controller logic اضافه کنیم که همه درخواست های ما را هندل کند. برای ایجاد یک controller، باید دستور زیر را در ترمینال اجرا کنید:

    $ php artisan make:controller NameController

از آنجا که ما درخواست های خود را هنگام استفاده از -mr قبلا ایجاد کرده ایم، می خواهیم آنها را ویرایش کنیم.

فایل کنترلر TaskController.php را در دایرکتوری app/Http/Controller/ باز کنید. در آن، ما چند متد اساسی برای مدیریت route هایی که در بالا ایجاد کردیم، تعریف خواهیم کرد.

در فایل، متد store را به صورت زیر آپدیت کنید:

    public function store(Request $request)
    {
        $task = Task::create([
            'name' => $request->name,
            'category_id' => $request->category_id,
            'user_id' => $request->user_id,
            'order' => $request->order
        ]);

        $data = [
            'data' => $task,
            'status' => (bool) $task,
            'message' => $task ? 'Task Created!' : 'Error Creating Task',
        ];

        return response()->json($data);
    }

 می بینیم که پاسخ به صورت JSON تنظیم شده است. شما می توانید مشخص کنید که داده ها در چه فرمت پاسخی برگردانده شوند، اما ما از JSON استفاده خواهیم کرد.

 ما در حال توضیح دادن چگونگی ایجاد RESTful endpoint هستیم. در قسمت های بعدی، کنترلرها را به طور کامل ایجاد خواهیم کرد.

Secure کردن endpoint ها با Passport

اکنون که route های خود را داریم، باید آنها را secure کنیم. الان هرکسی می تواند بدون نیاز به تأیید، به آنها دسترسی داشته باشد.

لاراول، به طور پیش فرض، ازroute های web و api پشتیبانی می کند. route های web مسیریابی را برای صفحات ایجاد شده به صورت داینامیک از طریق یک مرورگر وب کنترل می کنند، در حالی که route های API درخواست کلاینت هایی را که نیاز به response به دو فرمت JSON یا XML دارند، رسیدگی می کند.

Authentication و authorization (JWT) جهت ایمن سازی API ها

در قسمت اول این مجموعه، ما در مورد احراز هویت API با استفاده از Laravel Passport صحبت کردیم. در این بخش خیلی سریع موارد زیادی را مرور خواهیم کرد.

ابتدا Laravel Passport را نصب کنید:

    $ composer require laravel/passport  

Laravel Passport همراه با فایل های migration برای database table مورد نیاز کار است، بنابراین فقط باید آنها را اجرا کنید:

    $ php artisan migrate

بعد، باید دستور نصب passport را اجرا کنید تا بتواند کلیدهای لازم برای ایمن سازی برنامه را ایجاد کند:

    php artisan passport:install

این دستور کلیدهای encryption مورد نیاز برای تولید ایمن access token ها و "“personal access" و "password grant" را ایجاد می کند که برای تولید access token ها استفاده خواهد شد.

پس از نصب، باید از ویژگی Laravel Passport HasApiToken در User model استفاده کنید. این ویژگی چند متد helper برای مدل شما فراهم می کند که به شما امکان می دهد authenticated user ها را بررسی کنید.

File: app/User.php

    <‌?php

    [...]

    use Laravel\Passport\HasApiTokens;

    class User extends Authenticatable
    {
        use HasApiTokens, SoftDeletes, Notifiable;

        [...]
    }

در مرحله بعد، متد Passport::routes با متد boot  از AuthServiceProvider فراخوانی کنید. این متد route های لازم برای صدور token های مورد نیاز برنامه را رجیستر می کند:

فایل: app/Providers/AuthServiceProvider.php

    <‌?php

    [...]

    use Laravel\Passport\Passport;

    class AuthServiceProvider extends ServiceProvider
    {
        [...]

        public function boot()
        {
            $this--->registerPolicies();

            Passport::routes();
        }

        [...]
    }

سرانجام، در فایل پیکربندی config/auth.php خود، باید آپشن driver از api authentication را روی passport تنظیم کنید.

فایل: config/auth.php

    [...]

    'guards' => [
        [...]

        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
    ],

    [...]

Log in و register با استفاده از API

اکنون که احراز هویت API را برای این برنامه با استفاده از Laravel Passport تنظیم کرده اید، باید login و register را ایجاد کنیم.

route های زیر را در فایل routes/api.php اضافه کنید:

    Route::post('login', 'UserController@login');
    Route::post('register', 'UserController@register');

همچنین برای مدیریت authentication request های API باید UserController ایجاد کنید. یک فایل جدید UserController.php در app/Http/Controllers ایجاد کنید و کد زیر را در آن قرار دهید:

    <‌?php

    namespace App\Http\Controllers;

    use App\User;
    use Validator;
    use Illuminate\Http\Request;
    use App\Http\Controllers\Controller;
    use Illuminate\Support\Facades\Auth;

    class UserController extends Controller
    {
        public function login()
        {
            $credentials = [
                'email' =--> request('email'), 
                'password' => request('password')
            ];

            if (Auth::attempt($credentials)) {
                $success['token'] = Auth::user()->createToken('MyApp')->accessToken;

                return response()->json(['success' => $success]);
            }

            return response()->json(['error' => 'Unauthorised'], 401);
        }

        public function register(Request $request)
        {
            $validator = Validator::make($request->all(), [
                'name' => 'required',
                'email' => 'required|email',
                'password' => 'required',
            ]);

            if ($validator->fails()) {
                return response()->json(['error' => $validator->errors()], 401);
            }

            $input = $request->all();
            $input['password'] = bcrypt($input['password']);

            $user = User::create($input);
            $success['token'] = $user->createToken('MyApp')->accessToken;
            $success['name'] = $user->name;

            return response()->json(['success' => $success]);
        }

        public function getDetails()
        {
            return response()->json(['success' => Auth::user()]);
        }
    }

در کد بالا ما موارد زیر را داریم:

Login Method: در اینجا Auth::attempt را با credential یی که کاربر ارائه داده است فراخوانی می کنیم. اگر احراز هویت موفق باشد، access token هایی ایجاد می کنیم و آنها را به کاربر برمی گردانیم. این access token همان چیزی است که کاربر همیشه همراه با همه فراخوانی های API برای دسترسی به API ها ارسال می کند.

Register Method: مانند متد login، ما اطلاعات کاربر را تأیید کردیم، یک اکانت کاربری و یک access token برای کاربر ایجاد کردیم.

گروه بندی route ها تحت یک middleware مشترک

برای route های خود، می توانیم مسیرهایی را که برای احراز هویت نیاز داریم تحت middleware مشترک قرار دهیم. لاراول دارای auth:api middleware است و ما فقط می توانیم از آن برای ایمن سازی برخی route ها  استفاده کنیم همانطور که در فایل routes/api.phpمشاهده می کنید:

    <‌?php

    Route::post('login', 'UserController@login');
    Route::post('register', 'UserController@register');

    Route::group(['middleware' =--> 'auth:api'], function(){
        Route::resource('/task', 'TasksController');
        Route::resource('/category', 'CategoryController');
        Route::get('/category/{category}/tasks', 'CategoryController@tasks');
    });

مدیریت خطاهای API

درصورتی که سرور هنگام سرویس دهی یا دستکاری resource ها با خطایی روبرو شود، باید روشی را برای برقراری ارتباط با کلاینت اجرا کنیم که مشکلی پیش آمده است. برای این منظور، باید پاسخ ها را با کدهای خاص وضعیت HTTP ارائه دهیم.

اگر به فایل UserControlle``r.php نگاه کنید، مشاهده می کنید که کد وضعیت HTTP 401 را اجرا می کنیم که نشان می دهد client مجاز به مشاهده resource نیست:

    public function login(Request $request)
    {
        $status = 401;
        $response = ['error' => 'Unauthorised'];

        [...]

        return response()->json($response, $status);
    }

نتیجه

در این مقاله از مجموعه مقالات آموزش لاراول، ما یاد گرفته ایم که چگونه می توانید RESTful endpoint ها را برای برنامه خود ایجاد کنیم. همچنین آموختیم که چگونه می توانید خطاها را مدیریت کرده و کد وضعیت HTTP صحیح را به کلاینت ارائه دهیم.

در مقاله بعدی این مجموعه، به چگونگی تست API endpoint های خود با استفاده از postman خواهیم پرداخت. برخی از unit test ها را تنظیم خواهیم کرد که برای تست خود از command-line مفید خواهد بود.

× در حال پاسخ به: