티스토리 뷰

Laravel Swagger 쉽게 적용하기 (feat. vyuldashev/laravel-openapi 패키지 )

 

PHP에서 Swagger를 적용하면 코드보다 더 길은 주석을 보게 될 것이다.

Swagger annotations sample. Image by the author.

물론 Phpstorm에 플러그인을 달면 좀 편하게 쓸 수는 있지만,

이러한 단순 노가다를 하면서, 우리는 한 번쯤 이런 생각을 한다.

( 뭐 자동화나 클래스로 하는 방법은 없을까? )

 

PHP 쪽에도 이러한 움직임이 아예 없지는 않다. 그중 필자가 추천하는 패키지는 아래와 같다.

https://vyuldashev.github.io/laravel-openapi/

필자가 해당 패키지를 선택한 이유

1. 문서 작성을 주석에 의존하지 않고 재활용이 가능했으면 좋겠다.

2. 커스텀이 자유로웠으면 좋겠다 

 

위에 2가지가 모두 부합하여 선택하게 되었다. 

 

적용 한 모습 

https://vyuldashev.github.io/laravel-openapi/ 적용 모습

 

적용 방법

1. 패키지 설치 (1)

// composer 설치
composer require vyuldashev/laravel-openapi

// config 파일 publish
php artisan vendor:publish --provider="Vyuldashev\LaravelOpenApi\OpenApiServiceProvider" --tag="openapi-config"

// 설치 제대로 되었는지 확인
php artisan openapi:generate

vyuldashev/laravel-openapi 패키지는 json 문서를 클래스로 파일로 관리하게 해 준다.

 

2. 패키지 설치 (2)

// L5-Swagger 패키지 추가
composer require "darkaonline/l5-swagger"

// config publish
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"

vyuldashev/laravel-openapi 패키지는 json 파일을 생성해준다면, darkaonline/l5-swagger 패키지는 만들어진 json 파일을 읽어 화면이 뿌려주는 역할을 한다.

 

3. controller @method에 붙여보기

// controller 생성
php artisan make:controller TestController

 

[ProejctRoot]/app/Http/Controllers/TestController.php 생성된 Controller을 아래와 같이 수정해보자.

<?php

namespace App\Http\Controllers;

use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;

#[OpenApi\PathItem]
class TestController extends Controller
{
    /**
     * 이 자리에 제목이 들어갑니다..
     *
     * 이 자리에 설명이 들어갑니다.
     */
    #[OpenApi\Operation]
    public function index()
    {

    }
}

중요한 것은 Controller 위에 #[OpenAPi\PathItem]

적용할 method 에 #[OpenApi\Operation]을 달아준다.

method의 경우 예제와 같이 제목과 설명이 들어가는 주석을 달아줘야지 에러가 나지 않는다.

 

[ProjectRoot]/routes/api.php 파일을 수정해보자

<?php

use App\Http\Controllers\TestController;
use Illuminate\Support\Facades\Route;

Route::get('test', [TestController::class, 'index']);

이제 적용된 것이다.

 

제대로 적용되었는지 json파일을 뽑아보자 

php artisan openapi:generate > storage/api-docs/api-docs.json

 

이제 한번 브라우저에서 swagger의 경로에 들어가 보자

http://example.test/api/documentation

자신의 프로젝트 경로에 /api/documentation 에 들어가 보자.

경로는 config/l5-swagger.php 에서 바꿀 수 있다.

우리의 첫 swagger 가 완성되었다.

하지만 이렇게 작성하면 프론트 엔지니어에게 찍힌다.

보기 좋게 고도화를 해보자.

 

4. 고도화 (태그)

openapi의 설정 파일을 수정해 보자 config/openapi.php 에 있다.

collections.default.tags 에는 태그 배열이 들어간다 아래와 같이 고쳐보자.

'tags' => [
     [
        'name' => 'first-tag',
        'description' => '첫 태그를 달아보았습니다.',
     ],
    [
        'name' => 'last-tag',
        'description' => '마지막 태그를 달아보았습니다.',
    ],
],

 

그리고 아까 만들어 놓았던 TestController 도 수정해보자.

<?php

namespace App\Http\Controllers;

use App\OpenApi\Parameters\TestListParameters;
use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;

#[OpenApi\PathItem]
class TestController extends Controller
{
    /**
     * 테스트 목록
     *
     * 어떠한 것들을 받아서 어떻게 처리한 후에 테스트 목록을 보여준다.
     */
    #[OpenApi\Operation(tags: ['first-tag', 'last-tag'])]
//    #[OpenApi\Parameters(factory: TestListParameters::class)]
    public function index()
    {

    }

    /**
     * 테스트 상품 디테일
     *
     * 특정 테스트 항목을 자세히 보여준다
     */
    #[OpenApi\Operation(tags: ['last-tag'])]
    public function show()
    {

    }

    /**
     * 테스트 상품 생성
     *
     * 테스트를 생성한다
     */
    #[OpenApi\Operation(tags: ['first-tag'])]
    public function store()
    {

    }
}

show라는 method 가 추가되었으므로 [ProjectRoot]/routes/api.php 파일도 수정해보자

<?php

use App\Http\Controllers\TestController;
use Illuminate\Support\Facades\Route;

Route::get('test', [TestController::class, 'index']);
Route::get('show', [TestController::class, 'show']);
Route::post('test', [TestController::class, 'store']);

 

제대로 적용되었는지 json파일을 뽑아보자

php artisan openapi:generate > storage/api-docs/api-docs.json

이제 다시 브라우저에서 swagger의 경로에 들어가 보자

http://example.test/api/documentation

tags

swagger에 태그가 생긴 것을 확인할 수 있다.

클릭해보면 설명도 제대로 들어간 것을 확인할 수 있다.

 

5. 고도화 (Parameters)

이제 입력받을 사항들을 넣어 보자.

터미널에 아래와 같은 명령어를 쳐서 파일을 생성한다.

php artisan openapi:make-parameters TestList

그럼 파일이 생성된다.

app/OpenApi/Parameters/TestListParameters.php

특이점은 우리가 입력한 클래스명 뒤에 Parameters 가 자동으로 붙어서 생성된다.

아래와 같이 수정해보자

<?php

namespace App\OpenApi\Parameters;

use GoldSpecDigital\ObjectOrientedOAS\Objects\Parameter;
use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
use Vyuldashev\LaravelOpenApi\Factories\ParametersFactory;

class TestListParameters extends ParametersFactory
{
    public function build(): array
    {
        return [

            Parameter::query()
                ->name('name')
                ->description('(required) 이름을 입력')
                ->required()
                ->schema(Schema::string()),

            Parameter::query()
                ->name('mobile')
                ->description('(optional) 전화번호를 입력')
                ->required(false)
                ->schema(Schema::string()),
        ];
    }
}

return배열 안에 Parameter::query 하나당 하나의 Param이다.

schema 에는 자료형을 넣을 수 있는데 차후에 나오는 schema를 넣으면 모델 타입으로 한 번에 넣을 수도 있다.

이제 아까 만든 TestController에 적용해보자

<?php

namespace App\Http\Controllers;

use App\OpenApi\Parameters\TestListParameters;
use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;

#[OpenApi\PathItem]
class TestController extends Controller
{
    /**
     * 테스트 목록
     *
     * 어떠한 것들을 받아서 어떻게 처리한 후에 테스트 목록을 보여준다.
     */
    #[OpenApi\Operation(tags: ['first-tag', 'last-tag'])]
    #[OpenApi\Parameters(factory: TestListParameters::class)] // <--
    public function index()
    {

    }

    /**
     * 테스트 상품 디테일
     *
     * 특정 테스트 항목을 자세히 보여준다
     */
    #[OpenApi\Operation(tags: ['last-tag'])]
    public function show()
    {

    }
}

다시 json 파일을 생성하여 확인해보자

php artisan openapi:generate > storage/api-docs/api-docs.json

 

Parameter

Parameters가 추가되었다.

 

6. 고도화 (Request Body)

모든 rest-api 가 GET 방식으로 데이터를 주고받는 것은 아니다, request body를 적용해보자.

아래와 같이 명령어를 입력해보자.

php artisan openapi:make-requestbody TestStore

파일이 다음과 같은 경로에 생성되었다

app/OpenApi/RequestBodies/TestStoreRequestBody.php

param 때와 마찬가지로 우리가 정해준 이름 뒤에 RequestBody 가 붙었다.

다음과 같이 수정해보자.

<?php

namespace App\OpenApi\RequestBodies;

use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
use GoldSpecDigital\ObjectOrientedOAS\Objects\RequestBody;
use Vyuldashev\LaravelOpenApi\Factories\RequestBodyFactory;


class TestStoreRequestBody extends RequestBodyFactory
{
    public function build(): RequestBody
    {
        return RequestBody::create(self::class)
            ->description('테스트 생성')
            ->content(
                MediaType::json()->schema($this->testSchema())
            );
    }

    private function testSchema(): Schema
    {
        return Schema::object()->properties(
            Schema::integer('id')->example(1)->title('테스트 아이디'),
            Schema::string('name')->example('SPC-1')->title('테스트 상품 이름'),
            Schema::array('examples')->items(
                Schema::integer()->example(1),
            ),
        )->required('id', 'name');
    }
}

입력받는 부분 schema 부분이 재활용이 아쉬워 보일 것이다. 하지만 이 부분은 차후에 나오는 고도화 schema 부분에서

재활용이 가능하다.

 

TestController 에도 적용해보자

<?php

namespace App\Http\Controllers;

use App\OpenApi\Parameters\TestListParameters;
use App\OpenApi\RequestBodies\TestStoreRequestBody;
use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;

#[OpenApi\PathItem]
class TestController extends Controller
{
    /**
     * 테스트 목록
     *
     * 어떠한 것들을 받아서 어떻게 처리한 후에 테스트 목록을 보여준다.
     */
    #[OpenApi\Operation(tags: ['first-tag', 'last-tag'])]
//    #[OpenApi\Parameters(factory: TestListParameters::class)]
    public function index()
    {

    }

    /**
     * 테스트 상품 디테일
     *
     * 특정 테스트 항목을 자세히 보여준다
     */
    #[OpenApi\Operation(tags: ['last-tag'])]
    public function show()
    {

    }

    /**
     * 테스트 상품 생성
     *
     * 테스트를 생성한다
     */
    #[OpenApi\Operation(tags: ['first-tag'])]
    #[OpenApi\RequestBody(factory: TestStoreRequestBody::class)] // <---
    public function store()
    {

    }
}

 

다시 json 파일을 생성하여 확인해보자

php artisan openapi:generate > storage/api-docs/api-docs.json

 

request body

추가된 모습

 

7. 고도화 (Response)

이번에도 명령어로 cli 시작한다.

php artisan openapi:make-response Test

생성은 다음과 같은 경로에 생성된다.

app/OpenApi/Responses/TestResponse.php

이번에도 우리가 작성한 이름 뒤에 Response 가 붙어서 생성되었다.

이제 파일을 다음과 같이 수정해보자.

<?php

namespace App\OpenApi\Responses;

use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
use GoldSpecDigital\ObjectOrientedOAS\Objects\Response;
use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
use Vyuldashev\LaravelOpenApi\Factories\ResponseFactory;

class TestResponse extends ResponseFactory
{
    public function build(): Response
    {
        $response = Schema::object()->properties(
            Schema::integer('status_code')->example(200),
            Schema::string('message')->example('테스트 상품 등록 완료'),
        );

        return Response::create(self::class)
            ->description('테스트 상품 등록')
            ->content(MediaType::json()->schema($response));
    }
}

 

이번에도 역시 TestController에 적용해보자.

<?php

namespace App\Http\Controllers;

use App\OpenApi\Parameters\TestListParameters;
use App\OpenApi\RequestBodies\TestStoreRequestBody;
use App\OpenApi\Responses\TestResponse;
use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;

#[OpenApi\PathItem]
class TestController extends Controller
{
    /**
     * 테스트 목록
     *
     * 어떠한 것들을 받아서 어떻게 처리한 후에 테스트 목록을 보여준다.
     */
    #[OpenApi\Operation(tags: ['first-tag', 'last-tag'])]
//    #[OpenApi\Parameters(factory: TestListParameters::class)]
    public function index()
    {

    }

    /**
     * 테스트 상품 디테일
     *
     * 특정 테스트 항목을 자세히 보여준다
     */
    #[OpenApi\Operation(tags: ['last-tag'])]
    public function show()
    {

    }

    /**
     * 테스트 상품 생성
     *
     * 테스트를 생성한다
     */
    #[OpenApi\Operation(tags: ['first-tag'])]
    #[OpenApi\RequestBody(factory: TestStoreRequestBody::class)]
    #[OpenApi\Response(factory: TestResponse::class)] // <--
    public function store()
    {

    }
}

 

적용된 모습

response

response는 다음과 같이 여러 개도 적용 가능하다.

multiple response

 

8. 고도화 (Schema)

마지막이다 조금만 더 힘내자.

다음과 같이 cli 명령을 입력하자

php artisan openapi:make-schema Test

다음과 같이 파일이 생성되었다. app/OpenApi/Schemas/TestSchema.php

이번에는 우리가 입력한 글자 뒤에 Schema가 생겼다.

이번에는 아까 request body에서 생성했던, schema를 잘라와서 붙여 넣어 보자

<?php

namespace App\OpenApi\Schemas;

use GoldSpecDigital\ObjectOrientedOAS\Contracts\SchemaContract;
use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
use Vyuldashev\LaravelOpenApi\Contracts\Reusable;
use Vyuldashev\LaravelOpenApi\Factories\SchemaFactory;

class TestSchema extends SchemaFactory implements Reusable // <-- 이 인터페이스 꼭 넣어주세요
{
    public function build(): SchemaContract
    {
        return Schema::object()->properties(
            Schema::integer('id')->example(1)->title('테스트 아이디'),
            Schema::string('name')->example('SPC-1')->title('테스트 상품 이름'),
            Schema::array('examples')->items(
                Schema::integer()->example(1),
            ),
        )->required('id', 'name');
    }
}

 

아까 전에 생성했던 TestStoreRequestBody를 수정해보자

<?php

namespace App\OpenApi\RequestBodies;

use App\OpenApi\Schemas\TestSchema;
use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
use GoldSpecDigital\ObjectOrientedOAS\Objects\RequestBody;
use Vyuldashev\LaravelOpenApi\Factories\RequestBodyFactory;


class TestStoreRequestBody extends RequestBodyFactory
{
    public function build(): RequestBody
    {
        return RequestBody::create(self::class)
            ->description('테스트 생성')
            ->content(
                MediaType::json()->schema(TestSchema::ref()) // <--
            );
    }
}

다시 json 파일을 생성하여 확인해보자

php artisan openapi:generate > storage/api-docs/api-docs.json

 

바뀐 부분이 없을 것이다. schema를 활용하면 모델 단위로 재활용이 가능하다.

 

9. 마무리

소개한 기능 외에도 인증, schema의 고도화 등등 많지만 이 부분은 매뉴얼이 생각보다 불친절해서 고생 좀 했었다.

독자들은 나와 같은 문제가 없기를 바라며 질문 사항을 댓글에 달아주시면, 아는 선에서 답변해주겠다.

 

지루한 글 읽어줘서 고맙습니다.

 

 

ps..

오늘 소개한 라이브러리 말고도 후보에는 rakutentech/laravel-request-docs  패키지도 있다.

request와 resource 만 잘 써도 코드의 주석은커녕 첨부 없이도 자동으로 완성된다는 장점이 있지만,

설명글 및 태그를 커스텀할 수 없어서 후보에서 제외되었다.  하지만 독자들 중에는 필요한 사람이 있을지 몰라서 글을 남긴다.

댓글


최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday