Tích hợp CI/CD vào dự án Laravel API đơn giản

 


Khi nói đến việc xây dựng các API RESTful, Framework Laravel là lựa chọn hàng đầu. Đây là một trong những lý do tại sao Laravel vẫn là một trong 5 framework hàng đầu để phát triển web. Laravel cũng giúp việc thử nghiệm trở nên dễ dàng bằng cách cung cấp một bộ thử nghiệm dễ sử dụng để giúp bạn kiểm tra các điểm cuối API của mình. Trong bài đăng này, chúng tôi sẽ xây dựng một API xác thực dựa trên mã thông báo với Laravel, viết các bài kiểm tra cho các điểm cuối của chúng tôi và tự động hóa quá trình xây dựng và thử nghiệm với CircleCI.


Điều kiện tiên quyết

Để làm theo bài đăng này, bạn sẽ cần một số thứ:

  • PHP> = 7.1 được cài đặt trên hệ thống của bạn (bạn có thể xác nhận rằng phiên bản của bạn đủ cao bằng cách chạy lệnh php -v trên terminal của bạn)
  • Composer đã được cài (xác nhận điều này bằng cách chạy lện composer trên terminal của bạn)
  • Git đã được cài trên hệ thống của bạn
  • Tài khoản GitHub
  • Tài khoản CircleCI

Khi bạn đã thiết lập và chạy những thứ này, bạn sẽ sẵn sàng làm theo hướng dẫn.

Khởi tạo dự án Laravel API

Trong bài hướng dẫn này, SQLite sẽ được sử dụng để test và là CSDL chính. Thông thường, CSDL chính là các hệ QTCSDL phức tạp hơn như là MySQL hoặc MSSQL, nhưng chúng tôi sẽ giữ mọi thứ trong bài đăng này đơn giản, vì mục đích minh chứng. Chúng tôi sẽ giữ cấu hình cho cơ sở dữ liệu chính của chúng tôi trong tệp .env và sau đó tạo tệp .env.testing để chứa cấu hình cơ sở dữ liệu thử nghiệm của chúng tôi.

Bạn đã có tệp .env đi kèm với dự án mặc định. Tạo một tệp mới có tên .env.testing và sao chép nội dung của .env.example (cũng được tạo theo mặc định) vào đó.

Thay thế phần cấu hình cơ sở dữ liệu của cả hai tệp (.env và .env.testing) với cấu hình sau:

DB_CONNECTION=sqlite
DB_HOST=null
DB_PORT=null
DB_DATABASE=database/database.sqlite
DB_USERNAME=null
DB_PASSWORD=null

Chúng tôi đang sử dụng cùng một cấu hình cho cả hai tệp vì chúng tôi đang sử dụng cơ sở dữ liệu SQLite cho cả cơ sở dữ liệu chính và cơ sở dữ liệu thử nghiệm của chúng tôi. Trong đoạn mã trên, chúng tôi đã đặt kết nối của chúng tôi với sqlite và cơ sở dữ liệu của chúng tôi với database.sqlite được chứa trong thư mục database của chúng tôi. Chúng tôi vẫn chưa tạo tệp database.sqlite này, vì vậy hãy vào thư mục database của dự án của bạn và tạo tệp này.

Nhiệm vụ cuối cùng trong việc thiết lập cơ sở dữ liệu của chúng tôi là sử dụng một trong những trình trợ giúp của Laravel để trỏ đến tệp cơ sở dữ liệu SQLite của chúng tôi trong tệp cấu hình cơ sở dữ liệu của chúng tôi trong config/database.php. Bên trong tệp config/database.php , thay thế cấu hình SQLite trong mảng connections bằng cấu hình bên dưới::

'sqlite' => [
            'driver' => 'sqlite',
            'database' => database_path('database.sqlite'),
            'prefix' => '',
        ]

Điều này sẽ đảm bảo rằng cấu hình SQLite của chúng tôi luôn trỏ đến đường dẫn cơ sở dữ liệu chính xác nếu chúng tôi thay đổi môi trường mà ứng dụng của chúng tôi đang chạy.

Để xác nhận rằng tất cả đều hoạt động hoàn hảo, hãy di chuyển cơ sở dữ liệu của chúng tôi bằng cách chạy lệnh sau:

php artisan migrate

Quá trình di chuyển thành công sẽ hiển thị một cái gì đó tương tự như ảnh chụp màn hình bên dưới:

Đảm bảo rằng bạn bỏ qua tệp cơ sở dữ liệu bằng cách khai báo nó trong tệp .gitignore của bạn.

Cài đặt xác thực với Passport

Nhiệm vụ tiếp theo của chúng tôi là thiết lập dự án API của chúng tôi để sử dụng xác thực dựa trên token-based. Chúng tôi sẽ làm điều đó bằng cách sử dụng thư viện OAuth Passport Laravel cung cấp triển khai máy chủ OAuth2 đầy đủ cho ứng dụng Laravel của bạn. Đầu tiên, hãy cài đặt gói laravel/passport :

composer require laravel/passport v7.5.1

Sau khi cài đặt xong, hãy chạy lệnh sau để chạy quá trình migrate laravel/passport:

php artisan migrate

Thao tác này sẽ in ra một màn hình tương tự như màn hình bên dưới trên dòng lệnh của bạn:

Passport yêu cầu các khóa mã hóa để tạo access token, các khóa này cần được tạo và lưu trong cơ sở dữ liệu. Để tạo các khóa này, hãy chạy lệnh sau:

php artisan passport:install 

Sau khi chạy lệnh này thành công, bạn sẽ thấy các client secrets của mình được tạo.

Bước tiếp theo là thêm trait Laravel\Passport\HasApiTokens vào model App\User của bạn. Điều này sẽ giới thiệu các phương pháp trợ giúp từ gói laravel/passport package vào ứng dụng để giúp kiểm tra token và phạm vi của người dùng. Mở tệp app/User.php và thay thế nội dung của nó bằng mã bên dưới:

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens;

    
    protected $fillable = [
        'name', 'email', 'password',
    ];

   
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Trong tệp trên, chúng tôi đã nhập trait Laravel\Passport\HasApiTokens và hướng dẫn lớp chúng tôi sử dụng nó.

Tiếp theo, bạn cần gọi phương thức Passport::routes bên trong phương pháp khởi động của bạn AuthServiceProvider chứa bên trong tệp app/Providers/AuthServiceProvider.php . Mở tệp này và thay thế nội dung bằng mã bên dưới:

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

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

        Passport::routes();
    }
}

Cấu hình cuối cùng mà Passport yêu cầu là đặt nó là driver tùy chọn trong trình bảo vệ xác thực API bên trong config/auth.php.

'guards' => [
   'web' => [
       'driver' => 'session',
       'provider' => 'users',
   ],
   'api' => [
       'driver' => 'passport',
       'provider' => 'users',
   ],
], 

Với điều này, bây giờ ứng dụng sẽ sử dụng Passport’s TokenGuard để xác thực các yêu cầu gửi đến.

Tạo API

Chúng tôi sẽ xây dựng một API hồ sơ người dùng đơn giản. Người dùng sẽ có thể thực hiện những việc sau:

  • Đăng ký tài khoản mới
  • Đăng nhập vào tài khoản của họ bằng thông tin đăng nhập của họ
  • Tìm nạp hồ sơ của họ
  • Đăng xuất khỏi ứng dụng

Chúng tôi cần tạo các điểm cuối API cho bốn tác vụ này. Đi đến tệp routes/api.php và thay thế nội dung của nó bằng mã bên dưới:

<?php

use Illuminate\Http\Request;

Route::group([
    'prefix' => 'auth'
], function () {
    Route::post('login', 'AuthController@login');
    Route::post('signup', 'AuthController@signup');
  
    Route::group([
      'middleware' => 'auth:api'
    ], function() {
        Route::get('logout', 'AuthController@logout');
        Route::get('user', 'AuthController@user');
    });
});

Trong đoạn mã trên, chúng tôi tạo một route group auth và tạo bốn lộ trình cho bốn nhiệm vụ mà chúng tôi đã nêu trước đó.

Route auth/logout và  auth/user là được chứng thực với middleware auth:api để đảm bảo rằng chỉ có quyền truy cập đã xác thực mới được phép tới các điểm cuối này. Chúng tôi cũng đã tham khảo AuthController trên đó là controller mà chúng ta sẽ tạo tiếp theo. Trước đó, hãy xóa thư mục app/Http/Controllers/Auth cái mà theo mặc định với dự án. Sau đó, chạy lệnh sau để tạo controller:

php artisan make:controller AuthController

Bây giờ đặt đoạn code sau vào controller mới được tạo:

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;

class AuthController extends Controller
{
    /**
     * Create user
     *
     * @param  [string] name
     * @param  [string] email
     * @param  [string] password
     * @param  [string] password_confirmation
     * @return [string] message
     */
    public function signup(Request $request)
    {
        $request->validate([
            'name' => 'required|string',
            'email' => 'required|string|email|unique:users',
            'password' => 'required|string|confirmed'
        ]);        
        
        $user = new User([
            'name' => $request->name,
            'email' => $request->email,
            'password' => bcrypt($request->password)
        ]);        
        
        $user->save();        
        
        return response()->json([
            'message' => 'Successfully created user!'
        ], 201);
    }
  
    /**
     * Login user and create token
     *
     * @param  [string] email
     * @param  [string] password
     * @param  [boolean] remember_me
     * @return [string] access_token
     * @return [string] token_type
     * @return [string] expires_at
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
            'remember_me' => 'boolean'
        ]);

        $credentials = request(['email', 'password']);        
        if(!Auth::attempt($credentials)){
            return response()->json([
                'message' => 'Unauthorized'
            ], 401);
        }
                                
        $user = $request->user();        
        $tokenResult = $user->createToken('Personal Access Token');
        $token = $tokenResult->token;        
        
        if ($request->remember_me){
            $token->expires_at = Carbon::now()->addWeeks(1); 
        }
                   
        $token->save();        
        
        return response()->json([
            'access_token' => $tokenResult->accessToken,
            'token_type' => 'Bearer',
            'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ]);
    }
  
    /**
     * Logout user (Revoke the token)
     *
     * @return [string] message
     */
    public function logout(Request $request)
    {
        $request->user()->token()->revoke();        
        
        return response()->json([
            'message' => 'Successfully logged out'
        ]);
    }
  
    /**
     * Get the authenticated User
     *
     * @return [json] user object
     */
    public function user(Request $request)
    {
        return response()->json($request->user());
    }
} 

Ái chà! Đó là rất nhiều mã. Hãy xem qua code của từng phương thức.

Phương thức đăng ký

Phương thức này nhận email, mật khẩu và tên của người dùng dưới dạng các tham số, sau đó nó tạo ra một người dùng bằng cách sử dụng phương thức save() trên model  User và trả về một thông báo thành công cùng với trạng thái 201.

Phương thức đăng nhập

Phương thức này nhận email và mật khẩu của người dùng và một tùy chọn tham số remember_me . Sau đó, nó xác minh thông tin đăng nhập, và sau khi xác minh thành công, trả về access token, loại token và thời gian token sẽ hết hạn.

Phương thức đăng xuất

Phương thức này nhận và thu hồi mã thông báo truy cập được cấp cho người dùng, sau đó gửi thông báo thành công khi đăng xuất.

Phương thức người dùng

Phương thức này nhận access token của người dùng và sử dụng nó để trả lại thông tin chi tiết của người dùng.

Note: Nếu bạn thấy file .rnd được tạo trong dự án của bạn, bỏ qua nó bằng cách thêm vào file .gitignore 

Tạo testcase API

Bây giờ đã đến lúc viết một số thử nghiệm cho các điểm cuối API của chúng tôi. Hãy tạo một testcase bằng cách chạy lệnh sau:

php artisan make:test UserTest

Điều này tạo ra file test UserTest bên trong thư mục tests/Feature. Xóa file ExampleTest.php cái mà nằm mặc định trong dự án. Mở file UserTest.php vừa tạo và thay thế nội dung của nó bằng đoạn code dưới đây:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

use App\User;

class UserTest extends TestCase
{
   
    use WithFaker;

    private $password = "mypassword";
    
    public function testUserCreation()
    {
       	
       	$name = $this->faker->name();
       	$email = $this->faker->email();

        $response = $this->postJson('/api/auth/signup', [
            'name' => $name, 
            'email' => $email,
            'password' => $this->password, 
            'password_confirmation' => $this->password
        ]); 


        $response
            ->assertStatus(201)
            ->assertExactJson([
                'message' => "Successfully created user!",
            ]);
    }//testUserCreation
    
}

Tệp ở trên chứa một bài kiểm tra duy nhất kiểm tra điểm cuối API /api/auth/signup của chúng tôi để đảm bảo rằng người dùng có thể đăng ký thành công. Kiểm tra tạo một địa chỉ email ngẫu nhiên và tên người dùng bằng cách sử dụng thư viện PHP Faker đã được cài đặt sẵn cùng với mật khẩu chung để tạo tài khoản người dùng mới. Kiểm tra xác nhận trạng thái HTTP 201 (đã tạo) và thông báo phản hồi như được xác định trong bộ điều khiển của chúng tôi.

Chạy test nội bộ

Bây giờ để làm bài kiểm tra của chúng tôi cho một vòng quay. Chúng tôi sẽ sử dụng PHPUnit để chạy thử nghiệm của mình. Cài đặt PHPUnit đi kèm với lệnh phpunit CLI. Bạn có thể có lệnh này chạy trên toàn cầu với cài đặt toàn cầu của PHPUnit, hoặc có nó ở cấp độ dự án. Dự án Laravel có cấu trúc mặc định đã có gói cài đặt cục bộ cho phép chúng tôi chạy các thử nghiệm của mình.

Để chạy các bài kiểm tra, hãy chạy lệnh sau tại gốc dự án:

./vendor/bin/phpunit

Thao tác này chạy lệnh phpunit bằng cách sử dụng gói cài đặt cục bộ của chúng tôi. Nếu mọi thứ suôn sẻ, tất cả các bài kiểm tra của bạn sẽ chạy thành công và bạn sẽ có một màn hình tương tự như bên dưới.

Tự động tets với CircleCI

Đã đến lúc giới thiệu sức mạnh của CI/CD vào API Laravel của chúng tôi. Chúng tôi sẽ tạo một pipeline để đảm bảo rằng khi chúng ta đẩy code mới, các bài kiểm tra của chúng ta sẽ tự động chạy và chúng ta nhận được trạng thái pipeline CI/CD thành công hoặc không thành công. Chúng tôi sẽ sử dụng CircleCI để đạt được điều này. Quy trình của chúng tôi sẽ bao gồm các bước sau để cho phép chúng tôi đạt được mục tiêu của mình:

  • Quay vòng môi trường cần thiết
  • Cài đặt phụ thuộc với compose
  • Thiết lập tệp môi trường .env cho dự án
  • Thiết lập cơ sở dữ liệu thử nghiệm của chúng tôi và chạy migration của chúng tôi
  • Tạo khóa mã hóa cho gói laravel/passport 
  • Chạy thử nghiệm của chúng tôi

Để tạo pipeline CI/CD, chúng ta cần viết tệp cấu hình mà CircleCI sẽ sử dụng để thiết lập đường dẫn. Đi vào thư mục gốc của dự án của bạn và tạo một thư mục có tên .circleci. Bên trong thư mục này, tạo một tệp có tên config.yml.

Môi trường cần thiết

Trong bước này, chúng tôi sẽ pull circleci/php:7.4-node-browsers Docker image từ CircleCI image registry. Image này chứa PHP 7.4 được cài đặt cùng với Node và các trình duyệt. Sau đó, chúng tôi cập nhật môi trường của mình và cài đặt các gói cần thiết.

Bắt đầu tệp cấu hình bằng code bên dưới:

version: 2
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

Cài đặt dependencies với Composer

Bước tiếp theo là kiểm tra code của chúng tôi từ repository của chúng tôi vào môi trường đã được thiết lập và cài đặt các phụ thuộc dự án của chúng tôi. Chúng tôi cũng lưu trữ các phần phụ thuộc của mình vào bộ nhớ cache để tăng tốc độ pipeline của chúng tôi trong các lần chạy tiếp theo.

# Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

Thiết lập tệp môi trường .env cho dự án

Dự án Laravel của chúng tôi yêu cầu một tệp môi trường để chạy tệp .env .Tệp này là một tệp nhạy cảm, đó là lý do tại sao nó bị bỏ qua trong tệp .gitignore của chúng tôi .gitignore . Đây là lý do tại sao chúng tôi tạo một phiên bản khác của tệp .env.testing để lưu trữ thông tin đăng nhập chúng tôi cần để chạy các thử nghiệm của mình. Không giống như .env, .env.testing được đẩy vào repo của chúng tôi vì nó không chứa thông tin nhạy cảm.

Bây giờ chúng tôi sẽ đổi tên .env.testing của chúng tôi thành .env và tạo khóa ứng dụng cho ứng dụng Laravel

      - run:
          name: "Create Environment file and generate app key"
          command: |
            mv .env.testing .env
            php artisan key:generate

Thiết lập cơ sở dữ liệu testing và chạy migration

Nhiệm vụ tiếp theo là thiết lập cơ sở dữ liệu thử nghiệm và chạy migration của chúng tôi, hãy thực hiện điều đó bằng cách thêm bước sau:

      - run:
          name: "Create database and run migration"
          command: |
            touch database/database.sqlite
            php artisan migrate --env=testing

Tạo khóa mã hóa cho gói laravel/passport

Tiếp theo, chúng tôi tạo các khóa mã hóa theo yêu cầu của Passport để tạo mã thông báo truy cập.

      - run:
          name: "Generate Passport encryption keys"
          command: php artisan passport:install

Chạy tests

Cuối cùng, với môi trường và dự án của chúng tôi đã được thiết lập đầy đủ, bây giờ chúng tôi có thể chạy các thử nghiệm của mình bằng cách sử dụng cài đặt cục bộ của gói phpunit .

      - run:
          name: "Run Tests"
          command: ./vendor/bin/phpunit

Cấu hình đầy đủ

Đáng kinh ngạc. Bây giờ chúng tôi đã thiết lập tệp cấu hình của mình. File dưới đây là đầy đủ config.yml :

# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      # prepare the database
      - run:
          name: "Create Environment file and generate app key"
          command: |
            mv .env.testing .env
            php artisan key:generate

      - run:
          name: "Create database and run migration"
          command: |
            touch database/database.sqlite
            php artisan migrate --env=testing

      - run:
          name: "Generate Passport encryption keys"
          command: php artisan passport:install

      # run tests with phpunit
      - run:
          name: "Run Tests"
          command: ./vendor/bin/phpunit

Commit tất cả các thay đổi của bạn và đẩy vào remote repository của dự án.

Kết nối dự án API với CircleCI

Bây giờ đã đến lúc bàn giao cấu hình này cho CircleCI để thiết lập đường dẫn của chúng tôi. Chúng tôi làm điều đó bằng cách kết nối dự án của chúng tôi với CircleCI. Đi đến  CircleCI dashboard của bạn và thêm dự án trong phần Add Project.

Bên cạnh dự án của bạn, nhấn Set Up Project. Điều này sẽ đưa bạn đến một trang tương tự như bên dưới.

Nhấn vào Start building để bắt đầu xây dựng dự án của bạn. CircleCI sẽ bắt đầu chạy cấu hình đường dẫn của bạn. Nếu bạn đã làm theo đúng hướng dẫn, bạn sẽ có một bản dựng thành công được chỉ ra bởi màn hình bên dưới.

Nhấp vào quy trình làm việc này và cuộn xuống phần Run Tests để xem kết quả thử nghiệm của bạn.

Hoàn hảo. Chúng tôi đã có thể cung cấp thành công API Laravel của mình với một pipeline CI/CD tự động chạy thử nghiệm của chúng tôi bằng cách sử dụng CircleCI.

Hãy thêm một bài kiểm tra nữa vào file UserTest.php để kiểm tra điểm cuối /api/auth/login và đẩy code của chúng tôi để quan sát cách CircleCI tự động chạy thử nghiệm mới được thêm vào của chúng tôi. Thêm bài kiểm tra sau bên dưới testcase testUserCreation .

public function testUserLogin()
{
    $name = $this->faker->name();
   	$email = $this->faker->email();

    $user = new User([
        'name' => $name,
        'email' => $email,
        'password' => bcrypt($this->password)
    ]);        
    
    $user->save();     
    
    $response = $this->postJson('/api/auth/login', [
        'email' => $email,
        'password' => $this->password
    ]);

        
    $response->assertStatus(200);
    $this->assertAuthenticated();
}

Bây giờ hãy lưu tệp,commit và push các thay đổi của bạn lên GitHub. CircleCI sẽ một lần nữa chạy đường ống với các pipelines, bao gồm cả testing mà chúng tôi vừa thêm vào.

Tổng kết

Trong bài viết này, chúng tôi đã phát triển API xác thực dựa trên token với Laravel, đã thử nghiệm nó và tự động hóa quá trình xây dựng và thử nghiệm bằng cách sử dụng pipeline CI/CD do CircleCI tạo và chạy. Đây chỉ là điểm khởi đầu để đưa tự động hóa vào sự phát triển của các API Laravel của chúng tôi. Còn rất nhiều điều chúng ta có thể làm với CircleCI để tạo ra một pipeline phức tạp và mạnh mẽ hơn, loại bỏ rất nhiều tác vụ thủ công liên quan đến phát triển ứng dụng.

Tôi hy vọng bạn đã học được điều gì đó có giá trị từ bài đăng này. Happy Coding :)

0 Nhận xét