카테고리 없음

라라벨 Restful API 만들기

클레인 2018. 2. 20.
반응형

Laravel Restful API Tutorial

모바일 개발 및 JavaScript 프레임 워크의 등장으로 RESTful API를 사용하는 것이 데이터와 클라이언트 사이에 단일 인터페이스를 구축하는 최상의 방법입니다.

Laravel  PHP 개발자 생산성을 염두에두고 개발 된 PHP 프레임 워크입니다 . Taylor Otwell이 작성하고 유지 관리하는 프레임 워크는 매우 독창적이며 구성에 대한 관습을 선호하여 개발자의 시간을 절약하기 위해 노력하고 있습니다. 이 프레임 워크는 또한 웹을 통해 진화하는 것을 목표로하고 있으며 작업 대기열, 즉시 API 인증, 실시간 통신 등과 같은 웹 개발 세계에 몇 가지 새로운 기능과 아이디어를 이미 통합했습니다.

RESTful APIs

첫째, 정확하게 RESTful API로 간주되는 것이 무엇인지 이해해야합니다. REST는 REpresentational State Transfer의 약자 이며 상호 작용을 위해 상태 비 저장 프로토콜 (일반적으로 HTTP)에 의존하는 응용 프로그램 간의 네트워크 통신을위한 아키텍처 스타일입니다.

동작을 나타내는 HTTP 동사

RESTful API에서는 HTTP 동사를 작업으로 사용하고 끝점은 처리되는 리소스입니다. 의미 론적 의미로 HTTP 동사를 사용합니다.

  • GET: 자원 검색
  • POST: 자원 생성
  • PUT: 자원 업데이트
  • DELETE: 자원 삭제



업데이트 작업 : PUT 대 POST

편안하고 API는 많은 논쟁의 문제이며, 거기에 의견의 많음으로 업데이트하는 것이 가장 좋습니다 여부에있다 POST, PATCH또는 PUT경우, 또는 작성 작업이 가장 왼쪽되어 PUT동사. 이 기사에서는 PUTHTTP RFC에 따라 PUT특정 위치에 자원을 작성 / 갱신하는 것을 의미 하는 업데이트 조치에 사용할 것이다 . PUT동사에 대한 또 다른 요구 사항 은 멱수 (Idempotence)입니다.이 경우에는 기본적으로 요청을 1, 2 또는 1000 번 보낼 수 있으며 결과는 동일합니다. 데이터베이스에서 하나의 업데이트 된 리소스.

자원

리소스는 액션의 대상이 될 것이며, 우리의 경우 기사 및 사용자이며, 엔드 포인트가 있습니다.

  • /articles
  • /users

이 laravel API 튜토리얼에서 리소스는 데이터 모델에 대해 1 : 1 표현을 갖지만 요구 사항은 아닙니다. 하나 이상의 데이터 모델 (또는 데이터베이스에서 전혀 표현되지 않음)에 표현 된 리소스를 가질 수 있으며 사용자를위한 완전히 제한된 모델을 모델링 할 수 있습니다. 결국 애플리케이션에 적합한 방식으로 리소스 및 모델을 설계하는 방법을 결정하게됩니다.

일관성에 대한 참고 사항

REST와 같은 일련의 규칙을 사용할 때의 가장 큰 장점은 API를 사용하고 개발하는 것이 훨씬 쉬워진다는 점입니다. 일부 엔드 포인트는 결과로, 당신의 API 사용과 같은 엔드 포인트를 갖는 것 등의 반대 유지 관리가 훨씬 더 쉬워 질 것입니다, 매우 간단하고 GET /get_article?id_article=12 POST /delete_article?number=40. 나는 과거와 같은 끔찍한 API를 만들었지 만 여전히 나 자신을 싫어한다.

그러나 Create / Retrieve / Update / Delete 스키마에 매핑하는 것이 어려울 수 있습니다. URL에는 동사가 포함되어서는 안되며 리소스는 테이블의 행이 아니어야합니다. 또 다른 사실은 모든 리소스에 대해 모든 작업을 구현할 필요가 없다는 점입니다.

Laravel 웹 서비스 프로젝트 설정하기

모든 최신 PHP 프레임 워크와 마찬가지로 우리는 의존성을 설치하고 처리하기 위해 Composer  필요합니다 . 다운로드 지침에 따라 경로 환경 변수에 추가 한 후 다음 명령을 사용하여 Laravel을 설치하십시오.

$ composer global require laravel/installer

설치가 끝나면 다음과 같이 새 응용 프로그램을 비계 할 수 있습니다.

$ laravel new myapp

위의 명령에 대한 귀하의 필요가 ~/composer/vendor/bin있습니다 $PATH. 이 문제를 해결하고 싶지 않으면 Composer를 사용하여 새 프로젝트를 만들 수도 있습니다.

$ composer create-project --prefer-dist laravel/laravel myapp

Laravel을 설치하면 서버를 시작하고 모든 것이 작동하는지 테스트 할 수 있습니다.

$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>

localhost:8000브라우저에서 열면 이 샘플 페이지가 표시됩니다.

첫 번째 마이그레이션을 실제로 작성하기 전에이 응용 프로그램 용으로 데이터베이스를 만들고 .env해당 프로젝트의 루트에있는 파일에 자격 증명을 추가하십시오 .

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Laravel을 위해 특별히 제작 된 Vagrant 상자 인 Homestead를 사용할 수도 있습니다. 그러나이 글은이 기사의 범위를 벗어납니다. 더 자세히 알고 싶으면 홈스테드 문서를 참조하십시오 .

첫 번째 모델과 마이그레이션을 시작합시다. 기사에는 제목과 본문 필드 및 작성 날짜가 있어야합니다. Laravel은 Artisan-Laravel의 명령 줄 도구를 통해 몇 가지 명령을 제공합니다.이 도구는 파일을 생성하고 올바른 폴더에 넣는 데 도움이됩니다. 기사 모델을 만들려면 다음을 실행할 수 있습니다.

$ php artisan make:model Article -m

-m옵션에 대한 짧은 --migration그것은 우리의 모델을 만드는 장인을 말한다. 생성 된 마이그레이션은 다음과 같습니다.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

잠깐 해부 해 봅시다.

  •  메소드 up() down()메소드는 각각 마이그레이션 및 롤백 할 때 실행됩니다.
  • $table->increments('id')자동 증가 정수를 이름으로 설정합니다 id.
  • $table->timestamps()우리를 위해 타임 스탬프를 설정할 것입니다. created_at그리고 updated_at기본값을 설정하는 것에 대해 걱정하지 마십시오. Laravel은 필요할 때이 필드를 업데이트합니다.
  • 그리고 마지막으로 Schema::dropIfExists()테이블이 존재한다면 테이블을 떨어 뜨릴 것입니다.

그걸로 우리의 up()메서드에 두 줄을 추가하자 .

public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}

 string()메소드는 VARCHAR등가 컬럼을 text()작성하는 TEXT동등한 컬럼을 작성합니다 . 그렇게하면 마이그레이션하고 마이그레이션하십시오.

$ php artisan migrate

여기서도이 옵션 을 사용할 수 있으며 --step 필요한 경우 개별적으로 롤백 할 수 있도록 각 마이그레이션을 자체 배치로 분리합니다.

상자 밖으로 Laravel 두 마이그레이션 함께 제공 create_users_table하고 create_password_resets_table. 우리는 password_resets테이블을 사용하지 않을 것이지만 users우리 를 위해 테이블을 준비하는 것이 도움이 될 것입니다.

이제 모델로 돌아가 그 속성을 $fillable필드에 추가 하여 우리 Article::create Article::update모델 에서 사용할 수있게하십시오 .

class Article extends Model
{
    protected $fillable = ['title', 'body'];
}

$fillable 속성 안의 필드는 Eloquent create()  update() 메소드를 사용하여 질량을 할당 할 수 있습니다 . 속성을 사용하여 일부 속성을 제외한 모든 속성을 허용 할 수도 있습니다 $guarded .

데이터베이스 시드

데이터베이스 시드는 데이터베이스를 테스트하는 데 사용할 수있는 더미 데이터로 데이터베이스를 채우는 프로세스입니다. Laravel에는 Faker 가 있습니다. Faker 는 올바른 형식의 더미 데이터를 생성하는 훌륭한 라이브러리입니다. 첫 번째 시더를 만듭니다.

$ php artisan make:seeder ArticlesTableSeeder

시더는 /database/seeds디렉토리에 있습니다. 몇 가지 기사를 작성한 후에 표시되는 모습은 다음과 같습니다.

class ArticlesTableSeeder extends Seeder
{
    public function run()
    {
        // Let's truncate our existing records to start from scratch.
        Article::truncate();

        $faker = \Faker\Factory::create();

        // And now, let's create a few articles in our database:
        for ($i = 0; $i < 50; $i++) {
            Article::create([
                'title' => $faker->sentence,
                'body' => $faker->paragraph,
            ]);
        }
    }
}

seed 명령을 실행 해 봅시다.

$ php artisan db:seed --class=ArticlesTableSeeder

사용자 시드 작성 프로세스를 반복하십시오.

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        // Let's clear the users table first
        User::truncate();

        $faker = \Faker\Factory::create();

        // Let's make sure everyone has the same password and 
        // let's hash it before the loop, or else our seeder 
        // will be too slow.
        $password = Hash::make('toptal');

        User::create([
            'name' => 'Administrator',
            'email' => 'admin@test.com',
            'password' => $password,
        ]);

        // And now let's generate a few dozen users for our app:
        for ($i = 0; $i < 10; $i++) {
            User::create([
                'name' => $faker->name,
                'email' => $faker->email,
                'password' => $password,
            ]);
        }
    }
}

우리는 시드를 메인 DatabaseSeeder클래스의 database/seeds폴더 에 추가함으로써 더 쉽게 만들 수 있습니다 :

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(ArticlesTableSeeder::class);
        $this->call(UsersTableSeeder::class);
    }
}

이 방법으로 우리는 간단하게 실행할 수 있으며 메서드 $ php artisan db:seed에서 호출 된 클래스를 모두 실행합니다 run().

경로 및 컨트롤러

우리 응용 프로그램의 기본 끝점을 만들자 : 목록 만들기, 검색, 단일 검색, 업데이트 및 삭제.  routes/api.php파일, 우리는 단순히이 작업을 수행 할 수 있습니다

Use App\Article;
 
Route::get('articles', function() {
    // If the Content-Type and Accept headers are set to 'application/json', 
    // this will return a JSON structure. This will be cleaned up later.
    return Article::all();
});
 
Route::get('articles/{id}', function($id) {
    return Article::find($id);
});

Route::post('articles', function(Request $request) {
    return Article::create($request->all);
});

Route::put('articles/{id}', function(Request $request, $id) {
    $article = Article::findOrFail($id);
    $article->update($request->all());

    return $article;
});

Route::delete('articles/{id}', function($id) {
    Article::find($id)->delete();

    return 204;
})

내부 경로 api.php앞에는 접두어가 /api/붙고 API 억제 미들웨어가 자동으로이 경로에 적용됩니다 (접두사를 제거하려는 경우 RouteServiceProvider클래스를 편집 할 수 있음 /app/Providers/RouteServiceProvider.php).

이제이 코드를 컨트롤러로 옮겨 봅시다.

$ php artisan make:controller ArticleController

ArticleController.php :

use App\Article;
 
class ArticleController extends Controller
{
    public function index()
    {
        return Article::all();
    }
 
    public function show($id)
    {
        return Article::find($id);
    }

    public function store(Request $request)
    {
        return Article::create($request->all());
    }

    public function update(Request $request, $id)
    {
        $article = Article::findOrFail($id);
        $article->update($request->all());

        return $article;
    }

    public function delete(Request $request, $id)
    {
        $article = Article::findOrFail($id);
        $article->delete();

        return 204;
    }
}

routes/api.php파일 :

Route::get('articles', 'ArticleController@index');
Route::get('articles/{id}', 'ArticleController@show');
Route::post('articles', 'ArticleController@store');
Route::put('articles/{id}', 'ArticleController@update');
Route::delete('articles/{id}', 'ArticleController@delete');

암시 적 라우트 모델 바인딩을 사용하여 엔드 포인트를 향상시킬 수 있습니다. 이렇게하면 Laravel이 Article메서드에 인스턴스를 삽입하고 발견되지 않으면 자동으로 404를 반환합니다. 라우트 파일과 컨트롤러에서 변경해야합니다.

Route::get('articles', 'ArticleController@index');
Route::get('articles/{article}', 'ArticleController@show');
Route::post('articles', 'ArticleController@store');
Route::put('articles/{article}', 'ArticleController@update');
Route::delete('articles/{article}', 'ArticleController@delete');
class ArticleController extends Controller
{
    public function index()
    {
        return Article::all();
    }

    public function show(Article $article)
    {
        return $article;
    }

    public function store(Request $request)
    {
        $article = Article::create($request->all());

        return response()->json($article, 201);
    }

    public function update(Request $request, Article $article)
    {
        $article->update($request->all());

        return response()->json($article, 200);
    }

    public function delete(Article $article)
    {
        $article->delete();

        return response()->json(null, 204);
    }
}

HTTP 상태 코드 및 응답 형식에 대한 참고 사항

우리는 또한 response()->json()엔드 포인트에 호출을 추가했습니다 . 이를 통해 JSON 데이터를 명시 적으로 반환하고 클라이언트가 구문 분석 할 수있는 HTTP 코드를 보낼 수 있습니다. 반환 할 가장 일반적인 코드는 다음과 같습니다.

  • 200OK. 표준 성공 코드 및 기본 옵션.
  • 201: 객체가 생성되었습니다. store행동에 유용합니다 .
  • 204: 콘텐츠 없음. 작업이 성공적으로 실행되었지만 반환 할 콘텐츠가없는 경우.
  • 206: 부분 콘텐츠. 페이지 매김 된 리소스 목록을 반환해야 할 때 유용합니다.
  • 400: 잘못된 요청입니다. 유효성 검사를 통과하지 못한 요청에 대한 표준 옵션입니다.
  • 401: 무단으로. 사용자를 인증해야합니다.
  • 403: 금지됨. 사용자는 인증되었지만 작업을 수행 할 수있는 권한이 없습니다.
  • 404: 찾을 수 없습니다. 자원이 발견되지 않으면 Laravel에 의해 자동으로 반환됩니다.
  • 500: 인터넷 서버 오류. 이상적으로는 명시 적으로 반환하지 않을 것이지만 예상치 못한 문제가 발생하면 사용자가 받게 될 내용입니다.
  • 503: 서비스를 사용할 수 없습니다. 꽤 자명하지만 응용 프로그램에 의해 명시 적으로 반환되지 않는 또 다른 코드입니다.


app/Exceptions/Handler.phpJSON 응답을 반환하기 위해 에있는 예외 처리기 클래스를 편집하여이 문제를 해결할 수 있습니다 .
public function render($request, Exception $exception)
{
    // This will replace our 404 response with
    // a JSON response.
    if ($exception instanceof ModelNotFoundException) {
        return response()->json([
            'error' => 'Resource not found'
        ], 404);
    }

    return parent::render($request, $exception);
}

다음은 수익의 예입니다.

{
    data: "Resource not found"
}

Laravel을 사용하여 다른 페이지를 제공하는 경우 Accept헤더 로 작업하도록 코드를 편집해야합니다 . 그렇지 않으면 일반 요청의 404 오류가 JSON을 반환합니다.

public function render($request, Exception $exception)
{
    // This will replace our 404 response with
    // a JSON response.
    if ($exception instanceof ModelNotFoundException &&
        $request->wantsJson())
    {
        return response()->json([
            'data' => 'Resource not found'
        ], 404);
    }

    return parent::render($request, $exception);
}

이 경우 API 요청에 헤더가 필요합니다 Accept: application/json.

입증

Laravel에서 API 인증을 구현하는 방법에는 여러 가지가 있습니다 (그 중 하나 인 Passport 는 OAuth2를 구현하는 좋은 방법입니다).하지만이 기사에서는 매우 단순화 된 접근 방식을 취할 것입니다.

시작하려면 테이블에 api_token필드를 추가해야 users합니다.

$ php artisan make:migration --table=users adds_api_token_to_users_table

그런 다음 마이그레이션을 구현하십시오.

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('api_token', 60)->unique()->nullable();
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn(['api_token']);
    });
}

그 후에는 다음을 사용하여 마이그레이션을 실행하십시오.

$ php artisan migrate

레지스터 끝점 만들기

우리는 RegisterController( Auth폴더 안에) 등록을 할 때 올바른 응답을 되돌리기 위해 사용할 것입니다 . Laravel은 기본적으로 인증을 제공하지만, 우리가 원하는 응답을 얻기 위해서는 약간의 조정이 필요합니다.

컨트롤러는 특성 RegistersUsers을 이용하여 등록을 구현합니다. 다음은 작동 방식입니다.

public function register(Request $request)
{
    // Here the request is validated. The validator method is located
    // inside the RegisterController, and makes sure the name, email
    // password and password_confirmation fields are required.
    $this->validator($request->all())->validate();

    // A Registered event is created and will trigger any relevant
    // observers, such as sending a confirmation email or any 
    // code that needs to be run as soon as the user is created.
    event(new Registered($user = $this->create($request->all())));

    // After the user is created, he's logged in.
    $this->guard()->login($user);

    // And finally this is the hook that we want. If there is no
    // registered() method or it returns null, redirect him to
    // some other URL. In our case, we just need to implement
    // that method to return the correct response.
    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}

우리는 우리의 registered()메소드 를 구현할 필요가 RegisterController있습니다. 이 메소드는 the $request와 the를 받습니다. $user그래서 우리 모두가 원하는 것입니다. 다음은 컨트롤러 내부에서 메소드가 어떻게 보이는지를 보여줍니다.

protected function registered(Request $request, $user)
{
    $user->generateToken();

    return response()->json(['data' => $user->toArray()], 201);
}

그리고 우리는 그것을 라우트 파일에 링크 할 수 있습니다 :

Route::post(register, 'Auth\RegisterController@register);

위 섹션에서 사용자 모델에 대한 메소드를 사용하여 토큰을 생성했습니다. 이것은 우리가 토큰을 생성하는 단일 방법 만 가질 수 있도록 유용합니다. User 모델에 다음 메서드를 추가합니다.

class User extends Authenticatable
{
    ...
    public function generateToken()
    {
        $this->api_token = str_random(60);
        $this->save();

        return $this->api_token;
    }
}

 name, email, password, 및 password_confirmation필드가 필요하며, 피드백이 자동으로 처리됩니다. validator()내부  메소드를 점검하여 RegisterController규칙이 어떻게 구현되는지보십시오.

우리가 그 종말점에 도달했을 때 우리가 얻는 것은 다음과 같습니다 :

$ curl -X POST http://localhost:8000/api/register \
 -H "Accept: application/json" \
 -H "Content-Type: application/json" \
 -d '{"name": "John", "email": "john.doe@toptal.com", "password": "toptal123", "password_confirmation": "toptal123"}'
{
    "data": {
        "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT",
        "created_at": "2017-06-20 21:17:15",
        "email": "john.doe@toptal.com",
        "id": 51,
        "name": "John",
        "updated_at": "2017-06-20 21:17:15"
    }
}

로그인 엔드 포인트 만들기

등록 엔드 포인트와 마찬가지로 LoginController( Auth폴더에서) API 인증을 지원 하도록 편집 할 수 있습니다 . 특성  login메서드는 AuthenticatesUsersAPI를 지원하도록 재정의 될 수 있습니다.

public function login(Request $request)
{
    $this->validateLogin($request);

    if ($this->attemptLogin($request)) {
        $user = $this->guard()->user();
        $user->generateToken();

        return response()->json([
            'data' => $user->toArray(),
        ]);
    }

    return $this->sendFailedLoginResponse($request);
}

그리고 우리는 그것을 라우트 파일에 링크 할 수 있습니다 :

Route::post('login', 'Auth\LoginController@login');

이제 시더가 실행되었다고 가정하면 다음 POST과 같이 경로를 요청할 때 얻을 수 있습니다 .

$ curl -X POST localhost:8000/api/login \
  -H "Accept: application/json" \
  -H "Content-type: application/json" \
  -d "{\"email\": \"admin@test.com\", \"password\": \"toptal\" }"
{
    "data": {
        "id":1,
        "name":"Administrator",
        "email":"admin@test.com",
        "created_at":"2017-04-25 01:05:34",
        "updated_at":"2017-04-25 02:50:40",
        "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw"
    }
}

요청에서 토큰을 보내려면 api_token페이로드에 특성을 보내 거나 요청 헤더의 무기명 토큰으로를 사용하여 수행 할 수 있습니다 Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw.

로그 아웃하다

현재의 전략에서 토큰이 잘못되었거나 누락 된 경우 사용자는 인증되지 않은 응답을 받아야합니다 (다음 섹션에서 구현할 것입니다). 따라서 간단한 로그 아웃 엔드 포인트의 경우 토큰을 보내면 데이터베이스에서 제거됩니다.

routes/api.php:

Route::post('logout', 'Auth\LoginController@logout');

Auth\LoginController.php:

public function logout(Request $request)
{
    $user = Auth::guard('api')->user();

    if ($user) {
        $user->api_token = null;
        $user->save();
    }

    return response()->json(['data' => 'User logged out.'], 200);
}

이 전략을 사용하면 사용자가 소유하고있는 토큰은 모두 유효하지 않으며 API는 액세스를 거부합니다 (다음 섹션에서 설명하는 것처럼 미들웨어 사용). 모든 콘텐츠에 액세스하지 않고도 사용자가 로그인하지 못하도록 프런트 엔드와 조정해야합니다.

미들웨어를 사용하여 액세스 제한

으로 api_token만들어, 우리는 루트 파일에 인증 미들웨어를 전환 할 수 있습니다 :

Route::middleware('auth:api')
    ->get('/user', function (Request $request) {
        return $request->user();
    });

 $request->user()방법을 사용하거나 Auth 외관을 통해 현재 사용자에게 액세스 할 수 있습니다.

Auth::guard('api')->user(); // instance of the logged user
Auth::guard('api')->check(); // if a user is authenticated
Auth::guard('api')->id(); // the id of the authenticated user

그리고 우리는 다음과 같은 결과를 얻습니다 

이는 unauthenticatedHandler 클래스 에서 현재 메서드 를 편집해야하기 때문 입니다. 현재 버전은 요청에 Accept: application/json헤더 가있는 경우에만 JSON을 반환 하므로 변경하십시오.

protected function unauthenticated($request, AuthenticationException $exception)
{
    return response()->json(['error' => 'Unauthenticated'], 401);
}

이를 수정하면 기사 엔드 포인트로 되돌아 가서 auth:api미들웨어 로 래핑 할 수 있습니다 . 경로 그룹을 사용하여 다음과 같이 할 수 있습니다.

Route::group(['middleware' => 'auth:api'], function() {
    Route::get('articles', 'ArticleController@index');
    Route::get('articles/{article}', 'ArticleController@show');
    Route::post('articles', 'ArticleController@store');
    Route::put('articles/{article}', 'ArticleController@update');
    Route::delete('articles/{article}', 'ArticleController@delete');
});

이렇게하면 각 경로에 대해 미들웨어를 설정할 필요가 없습니다. 현재 많은 시간을 절약하지는 못하지만 프로젝트가 성장함에 따라 DRY 경로를 유지하는 것이 도움이됩니다.

엔드 포인트 테스트

Laravel에는 PHPUnit과의 통합이 phpunit.xml이미 포함되어 있습니다. 또한 프레임 워크는 API를 테스트 할 때 특히 도움이되는 몇 가지 도우미와 추가 어설 션을 제공합니다.

API를 테스트하는 데 사용할 수있는 여러 가지 외부 도구가 있습니다. 그러나 Laravel에서 테스트하는 것이 훨씬 더 나은 대안입니다. 우리는 데이터베이스의 완전한 제어를 유지하면서 API 구조와 결과를 테스트하는 모든 이점을 누릴 수 있습니다. 예를 들어리스트 엔드 포인트의 경우, 두 개의 팩토리를 실행하고 응답에 해당 자원이 포함되어 있다고 주장 할 수 있습니다.

시작하려면 인 메모리 SQLite 데이터베이스를 사용하기 위해 몇 가지 설정을 조정해야합니다. 이를 사용하면 테스트가 번개처럼 빠르게 진행되지만, 특정 설정에서 일부 마이그레이션 명령 (예 : 제약)이 제대로 작동하지 않는다는 단점이 있습니다. 마이 그 레이션 오류가 발생하기 시작하거나 퍼포먼스 실행 대신 강력한 테스트 세트를 선호하는 경우 SQLite에서 테스트 해보십시오.

또한 각 테스트 전에 마이그레이션을 실행합니다. 이 설정을 사용하면 각 테스트에 대한 데이터베이스를 빌드 한 다음 테스트간에 종속성을 피할 수 있도록 데이터베이스를 작성할 수 있습니다.

우리 config/database.php파일에서 우리는 설정에서 database필드를 다음과 같이 sqlite설정해야합니다 :memory::

...
'connections' => [

    'sqlite' => [
        'driver' => 'sqlite',
        'database' => ':memory:',
        'prefix' => '',
    ],
    
    ...
]

그런 다음 phpunit.xml환경 변수를 추가하여 SQLite를 활성화 합니다 DB_CONNECTION.

    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="DB_CONNECTION" value="sqlite"/>
    </php>

그렇게하지 않으면 남아있는 모든 것이 TestCase마이그레이션을 사용하고 각 테스트 전에 데이터베이스를 시드 하도록 기본 클래스를 구성하는 것 입니다. 이렇게하려면 DatabaseMigrations특성을 추가 한 다음 메서드에 Artisan호출 을 추가해야합니다 setUp(). 변경 후의 클래스는 다음과 같습니다.

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseMigrations;

    public function setUp()
    {
        parent::setUp();
        Artisan::call('db:seed');
    }
}

마지막으로 테스트 명령을 추가하는 것입니다 composer.json.

    "scripts": {
        "test" : [
            "vendor/bin/phpunit"
        ],
    ... 
    },    

테스트 명령은 다음과 같이 사용할 수 있습니다.

$ composer test

테스트를위한 공장 설립

팩토리를 사용하면 테스트를 위해 올바른 데이터로 객체를 빠르게 생성 할 수 있습니다. 그들은에 위치하고있는 database/factories폴더. Laravel은 User클래스에 대한 팩토리로 상자에서 나오 므로 클래스에 하나를 추가해 보겠습니다 Article.

$factory->define(App\Article::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph,
    ];
});

위조자의 라이브러리는 이미 우리가 우리의 모델에 대한 임의의 데이터의 올바른 형식을 만드는 데 도움이 주입된다.

첫 번째 테스트

우리는 Laravel의 주장 방법을 사용하여 손쉽게 엔드 포인트에 도달하고 응답을 평가할 수 있습니다. 다음 명령을 사용하여 첫 번째 테스트 인 로그인 테스트를 작성해 보겠습니다.

$ php artisan make:test Feature/LoginTest

그리고 우리의 시험은 다음과 같습니다.

class LoginTest extends TestCase
{
    public function testRequiresEmailAndLogin()
    {
        $this->json('POST', 'api/login')
            ->assertStatus(422)
            ->assertJson([
                'email' => ['The email field is required.'],
                'password' => ['The password field is required.'],
            ]);
    }


    public function testUserLoginsSuccessfully()
    {
        $user = factory(User::class)->create([
            'email' => 'testlogin@user.com',
            'password' => bcrypt('toptal123'),
        ]);

        $payload = ['email' => 'testlogin@user.com', 'password' => 'toptal123'];

        $this->json('POST', 'api/login', $payload)
            ->assertStatus(200)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'name',
                    'email',
                    'created_at',
                    'updated_at',
                    'api_token',
                ],
            ]);

    }
}

이 방법은 몇 가지 간단한 경우를 테스트합니다.  json()메서드는 끝점에 도달하고 다른 어설 션은 매우 자명합니다. 약 상세 assertJson():이 방법은 인수 배열 검색에 응답하여 변환하므로 순서는 중요하다. assertJson()이 경우 여러 통화를 연결할 수 있습니다 .

이제 레지스터 끝점 테스트를 만들고 그 끝점에 대한 몇 가지 코드를 작성해 보겠습니다.

$ php artisan make:test RegisterTest
class RegisterTest extends TestCase
{
    public function testsRegistersSuccessfully()
    {
        $payload = [
            'name' => 'John',
            'email' => 'john@toptal.com',
            'password' => 'toptal123',
            'password_confirmation' => 'toptal123',
        ];

        $this->json('post', '/api/register', $payload)
            ->assertStatus(201)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'name',
                    'email',
                    'created_at',
                    'updated_at',
                    'api_token',
                ],
            ]);;
    }

    public function testsRequiresPasswordEmailAndName()
    {
        $this->json('post', '/api/register')
            ->assertStatus(422)
            ->assertJson([
                'name' => ['The name field is required.'],
                'email' => ['The email field is required.'],
                'password' => ['The password field is required.'],
            ]);
    }

    public function testsRequirePasswordConfirmation()
    {
        $payload = [
            'name' => 'John',
            'email' => 'john@toptal.com',
            'password' => 'toptal123',
        ];

        $this->json('post', '/api/register', $payload)
            ->assertStatus(422)
            ->assertJson([
                'password' => ['The password confirmation does not match.'],
            ]);
    }
}

마지막으로 로그 아웃 엔드 포인트 :

$ php artisan make:test LogoutTest
class LogoutTest extends TestCase
{
    public function testUserIsLoggedOutProperly()
    {
        $user = factory(User::class)->create(['email' => 'user@test.com']);
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        $this->json('get', '/api/articles', [], $headers)->assertStatus(200);
        $this->json('post', '/api/logout', [], $headers)->assertStatus(200);

        $user = User::find($user->id);

        $this->assertEquals(null, $user->api_token);
    }

    public function testUserWithNullToken()
    {
        // Simulating login
        $user = factory(User::class)->create(['email' => 'user@test.com']);
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        // Simulating logout
        $user->api_token = null;
        $user->save();

        $this->json('get', '/api/articles', [], $headers)->assertStatus(401);
    }
}

테스트하는 동안 Laravel 응용 프로그램은 새 요청에 대해 다시 인스턴스화되지 않습니다. 즉, 인증 미들웨어에 도달하면 현재 사용자를 인스턴스에 저장 TokenGuard 하여 데이터베이스에 다시 도달하지 않도록합니다. 그러나 현명한 선택은 이전에 캐시 된 사용자의 문제를 피하기 위해 로그 아웃 테스트를 두 개로 나눠야한다는 것을 의미합니다.

기사 엔드 포인트 테스트는 간단합니다.

class ArticleTest extends TestCase
{
    public function testsArticlesAreCreatedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $payload = [
            'title' => 'Lorem',
            'body' => 'Ipsum',
        ];

        $this->json('POST', '/api/articles', $payload, $headers)
            ->assertStatus(200)
            ->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']);
    }

    public function testsArticlesAreUpdatedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $article = factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body',
        ]);

        $payload = [
            'title' => 'Lorem',
            'body' => 'Ipsum',
        ];

        $response = $this->json('PUT', '/api/articles/' . $article->id, $payload, $headers)
            ->assertStatus(200)
            ->assertJson([ 
                'id' => 1, 
                'title' => 'Lorem', 
                'body' => 'Ipsum' 
            ]);
    }

    public function testsArtilcesAreDeletedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $article = factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body',
        ]);

        $this->json('DELETE', '/api/articles/' . $article->id, [], $headers)
            ->assertStatus(204);
    }

    public function testArticlesAreListedCorrectly()
    {
        factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body'
        ]);

        factory(Article::class)->create([
            'title' => 'Second Article',
            'body' => 'Second Body'
        ]);

        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        $response = $this->json('GET', '/api/articles', [], $headers)
            ->assertStatus(200)
            ->assertJson([
                [ 'title' => 'First Article', 'body' => 'First Body' ],
                [ 'title' => 'Second Article', 'body' => 'Second Body' ]
            ])
            ->assertJsonStructure([
                '*' => ['id', 'body', 'title', 'created_at', 'updated_at'],
            ]);
    }

}

다음 단계

Passport 패키지로 OAuth2를 구현 하고, 페이지 매김과 변환 레이어를 통합 할 수 있습니다. (Fractal을 권장 합니다.) 
Laravel  PHP 에 대한 내 경험을 확실히 향상 시켰 으며 테스트의 용이성으로 인해 프레임 워크에 대한 관심이 높아졌습니다. 완벽하지는 않지만 문제를 해결할 수있을만큼 유연합니다.


출처 : https://www.toptal.com/laravel/restful-laravel-api-tutorial

반응형

댓글