Eloquent Result에 순번 추가하기

Laravel에서 Eloquent ORM을 다룰 때 Resultset에 커스텀 필드를 추가해야 할 경우가 있다.

대표적으로 순번 필드가 있는데, 고유번호(id) 필드와는 별도로 item마다 순번을 부여하여 취급하고자 하는 경우이다.

간단한 필드의 경우 Mutators를 이용할 수 있지만, 순번과 같이 아이템의 개수와 페이지에 따라 동적으로 값이 부여되어야 할 경우 Controller에서 생성해주어야 한다.

 

다음은 User 모델의 항목들을 10개 단위로 Pagination 하는 예제이다.

public function index()
{
    $users = User::paginate(10);

    $response = [
        'pagination' => [
            'total' => $users->total(),
            'per_page' => $users->perPage(),
            'current_page' => $users->currentPage(),
            'last_page' => $users->lastPage(),
            'from' => $users->firstItem(),
            'to' => $users->lastItem()
        ],
        'data' => $users
    ];

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

여기서 number라는 순번 필드를 $users에 추가하고자 하는 경우 다음과 같이 코드를 수정한다.

public function index()
{
    $users = User::paginate(10);

    $total = $users->total();
    $per_page = $users->perPage();
    $current_page = $users->currentPage();
    $count = 0;

    // 순번 필드를 추가하여 transform
    $users->getCollection()->transform(function ($user) use ($total, $per_page, $current_page, &$count) {
        $user->number = ($total - ($per_page * ($current_page - 1))) - $count;
        $count++;
        return $user;
    });

    $response = [
        'pagination' => [
            'total' => $total,
            'per_page' => $per_page,
            'current_page' => $current_page,
            'last_page' => $users->lastPage(),
            'from' => $users->firstItem(),
            'to' => $users->lastItem()
        ],
        'data' => $users
    ];

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

이렇게 하면 고유번호와는 별개로 순번 필드인 number가 각 item마다 추가된다. 페이지를 이동하여도 각 페이지에 맞게 순번이 올바르게 부여되는 것을 확인할 수 있다.

 

view에서 출력할 때 해당 필드를 적절히 이용하면 된다.

Laravel Eloquent ORM transform

Laravel에 AdminLTE Template 통합하기

AdminLTE는 무료 오픈소스 대시보드 템플릿이다. 본 포스트에서는 AdminLTE를 Laravel과 통합하는 방법을 설명하고자 한다.

Laravel 프로젝트에서 다음 명령어를 입력하여 AdminLTE 템플릿을 npm으로 설치한다.
(bower로 설치하는 방법도 있지만 Laravel Mix와 연계를 위해 가급적 npm을 사용하는 것이 좋다고 생각한다.)

npm install admin-lte --save-dev

resources/sass 경로에 admin.scss (이름은 자유롭게 한다.) 파일을 하나 생성하고, 코드를 다음과 같이 입력한다.

// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

// Variables
@import 'variables';

// Font awesome
@import '~font-awesome/scss/font-awesome';

// Bootstrap3
@import '~admin-lte/bower_components/bootstrap/dist/css/bootstrap.css';

// Admin LTE
@import '~admin-lte/dist/css/AdminLTE.css';
@import '~admin-lte/dist/css/skins/_all-skins.css';
@import '~admin-lte/bower_components/Ionicons/scss/ionicons';

참고로 AdminLTE 2.x 버전은 Bootstrap 3에 의존성을 가진다. 2018년 12월 기준 현재 Laravel 5.7 버전은 Bootstrap 4 버전을 내장하고 있기 때문에 기존의 app.scss 파일에 있는 Bootstrap 4를 그대로 사용하면 스타일이 깨져 나온다. admin-lte에 내장된 Bootstrap 3를 사용하도록 한다.

resources/js 경로에 admin.js 파일을 하나 생성하고 코드를 다음과 같이 입력한다.

require('admin-lte');
require('admin-lte/bower_components/chart.js');

webpack.min.js 파일은 다음과 같이 수정한다.

mix.js('resources/js/app.js', 'public/js')
    .js('resources/js/admin.js', 'public/js')

    .sass('resources/sass/app.scss', 'public/css')
    .sass('resources/sass/admin.scss', 'public/css')

    .extract([
        'jquery'
    ])

    .autoload({
        jquery: ['$', 'jQuery', 'jquery'],
    })

    .version();

중간에 autoload 메소드에 jquery를 로드하는 부분 없이 그냥 jquery만 vendor에 통합하면 Error가 발생한다.

다음으로, DashboardController를 생성한다.

php artisan make:controller DashboardController

app/Http/Controllers/DashboardController.php를 아래와 같이 편집한다.

class DashboardController extends Controller
{
    public function index()
    {
        return view('dashboard');
    }
}

routes/web.php를 아래와 같이 편집한다.

Route::get('/admin', 'DashboardController@index')->name('dashboard');

resources/views/layouts 에 다음과 같이 레이아웃 파일을 생성한다. 레이아웃 형식은 자유롭게 작성한다.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title></title>

    <link href="{{ asset('css/admin.css') }}" rel="stylesheet">
</head>
<body class="hold-transition skin-blue sidebar-mini">

<div id="app">
    @yield('content')
</div>

<!-- Scripts -->
<script src="{{ mix('js/manifest.js') }}"></script>
<script src="{{ mix('js/vendor.js') }}"></script>
<script src="{{ mix('js/admin.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>

@yield('script')
</body>
</html>

이제 resources/views에 dashboard.blade.php 뷰 파일을 생성하고 AdminLTE 데모 페이지의 소스를 참고해 내용을 채워넣는다.

js와 sass파일을 컴파일한다.

npm run prod

 

Develop Laravel AdminLTE

Laravel Dusk에서 로그인 테스트 시 이전 세션이 남아있는 문제 해결하기

Laravel Dusk에서 로그인 테스트를 하고자 할 경우 다음과 같이 코드를 작성 할 수 있다.

 

/** @test */
public function 로그인한다()
{
    $this->browse(function (Browser $browser) {
        $browser->visit(route('login'))
            ->type('email', 'myemail@address.com')
            ->type('password', 'password')
            ->press('Login')
            ->assertRouteIs('home');
    });
}

/** @test */
public function 사용자_입력값이_유효하지_않으면_오류난다()
{
    $this->browse(function (Browser $browser) {
        $browser->visit(route('login'))
            ->type('email', 'test@testdomain.com')
            ->type('password', '123456')
            ->press('Login')
            ->assertSee(trans('auth.failed'));
    });
}

 

첫번째 로그인한다()는 정상적으로 작동하지만, 사용자_입력값이_유효하지_않으면_오류난다() 메서드는 오류가 발생한다. 이전 로그인 세션이 남아있기 때문이다.

이를 해결하기 위해서는 DuskTestCase.php에 다음과 같이 setUp() 메서드를 오버라이딩하고, 쿠키 삭제 코드를 추가한다.

 

protected function setUp()
{
    parent::setUp();
    foreach (static::$browsers as $browser) {
        $browser->driver->manage()->deleteAllCookies();
    }
}

 

Laravel Laravel Dusk Testing