본문으로 건너뛰기

"laravel" 태그로 연결된 19개 게시물개의 게시물이 있습니다.

모든 태그 보기

· 약 2분

Laravel에서 paginate 메소드를 json으로 받았을 시에 데이터는 다음과 같다.

response
{
"current_page": 1,
"data": [{}, {}, {}],
"from": 1,
"last_page": 1,
"next_page_url": null,
"path": "https://example.com/ajax/list",
"per_page": 15,
"prev_page_url": null,
"to": 8,
"total": 8
}

이 데이터를 blade에서 links 메소드로 쉽게 사용할 수 있는 것 처럼 Vue에서도 그럴 수는 없을까?

Laravel Vue Pagination 패키지를 활용하면 된다. SPA일 경우에는 다운 받고 Usage 탭에 적힌대로 바로 사용하면 되지만, Multi Page일경우는 직접 컴포넌트를 가져와야한다.

<ul>
<li v-for="post in response.data" v-text="post.title"></li>
</ul>

<nav>
<pagination
:data="response"
:limit="3"
@pagination-change-page="fetchPosts"
/>
</nav>

<script>
new Vue({
data: {
response: {
data: [],
},
},

methods: {
fetchPosts: function (page) {
var vm = this;
axios
.get("url", {
params: {
page: page || 1,
},
})
.then(function (response) {
vm.response = response;
});
},
},
});
</script>

예외처리

Component 소스template 부분을 보면 nav로 감싸져있지 않기에 Laravel 기본 템플릿과 일치시키려면 nav 태그로 감싸주면 된다.

· 약 1분

Model Collection의 데이터를 처리하는 메소드를 사용하고 싶을 때 아래처럼 접근하면 된다.

회원 모델과 사용가능한 포인트를 산출하는 availablePoint 메소드가 있다고 가정한다.

<?php
$members = Member::where('status', 1)
->get()
->map(function($member) {
// Model에서 정의된 filter 메소드를 적용할 수 있다.
return $member->availablePoint();
});

// pagination 에서 사용하는 방법
$members = Member::where('status', 1)->paginate();
// 쉬운 방법
$members->map(function($member) {
return $member->availablePoint();
});

// 긴 방법
//$members->getCollection()->transform(function($member) {
// return $member->availablePoint();
//});

// pagination에서 data 필드만 필요하다면
$members = Member::where('status', 1)
->paginate()
->map(function($member) {
return $member->availablePoint();
});

· 약 3분

라라벨 이벤트 리스너 기능을 붙혀보자. Model이 Create 될 때 이벤트 리스너를 붙혀 다른 기능을 연결하는 예제가 가장 쉽다. (예를 들면 로그가 생성될 때 SMS를 날리는 경우)

EventServiceProvider

먼저 EventServiceProvider에 내가 사용할 이벤트와 리스너를 등록해줘야한다.

app/Providers/EventServiceProvider
<?php
...
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
// 로그 생성시 이벤트를
'App\Events\LogCreated' => [
// 로그 생성됨 리스너에 연결시켜준다.
'App\Listeners\LogCreatedListener',
],
];

$listen 변수에 기본으로 등록되어있는 이벤트는 지워주자

generate

이제 소스 파일을 생성시켜준다.

$ php artisan event:generate

명령어를 실행하면 app/Eventsapp/Listeners에 방금 등록한 이벤트 리스너 파일이 자동으로 생성된다.

바인딩

모델

모델에서 방금 추가된 이벤트를 연결시켜주자.

app/Models/Log
<?php
use App\Events\LogCreated;

class Log extends Model
{
...
protected $dispatchesEvents = [
// 모델이 create(insert) 되면 해당 이벤트를 호출한다.
'created' => LogCreated::class
// use 구문을 사용하지 않고 여기에 직접 "App\Events\LogCreated" 로 정의해도 될 것 같은데 테스트는 안 해봤다.
];
}

이벤트

이벤트에서 해당 모델을 연결시켜주자.

app/Events/LogCreated
<?php
use App\Models\Log;
...

class LogCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;

// 리스너에서 받을 모델 변수를 public으로 생성한다.
public $log;

// DI
public function __construct(Log $log)
{
$this->log = $log;
}

public function broadcastOn()
{
// 채널을 이용하지 않을 것이기에 빈 배열을 리턴시킨다.
return [];
}
}

처리

리스너에서 받은 이벤트를 처리하자.

app/Listeners/LogCreatedListener
<?php
...
use App\Events\LogCreated;

class LogCreatedListener
{
...
public function handle(LogCreated $event)
{
// Events의 public으로 선언한 데이터가 $event 아래로 바인딩 된다.

$log = $event->log;
logger('LOG Received');
logger($log);

// 여기서 기능을 구현하면 된다.
}
}

ShouldQueue로 확장해 큐에 담을 수도 있다.

여담

메일 발송과 비슷한 플로우였다.

· 약 3분

검색해 나온 포스트들은 5.4버전에 대해서만 나와있어서, 5.5에서는 아무짝에 쓸모가 없었다. 라라벨에서 좃인증을 시작해보자.

jwt-auth

171103 기준으로 dev-develop 버전의 패키지를 설치해야한다.

$ composer require tymon/jwt-auth:1.0.0-rc.1

service provider 등록

config/app.php
<?php

'providers' => [
...
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
],

'alias' => [
...
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class
],

설정파일 publish

$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" --force

secret key 생성

$ php artisan jwt:secret

연동

API Route 설정

API 가드와 유저 모델을 설정하다.

config/auth.php
<?php
return [
'defaults' => [
'guard' => 'api', // 기본 가드를 api로 변경
'passwords' => 'users',
],

'guards' => [
...
'api' => [
'driver' => 'jwt', // api 가드를 jwt 인증을 사용
'provider' => 'users',
],
],

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\Member::class, // 유저 모델을 해당 모델로 변경
],
],
...
];

Member Model 설정

app/Models/Member.php
<?php
...
// jwt를 모델에서 사용하기 위해 추가한다.
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
...

class Member extends Authenticatable implements JWTSubject
{
// 아래 두 메소드가 구현되어야 실행된다.
public function getJWTIdentifier() {
return $this->getKey();
}

public function getJWTCustomClaims() {
return [];
}
}

사용하기

login

app/Http/MemberController.php
<?php
public function login(Request $request) {
$credentials = $this->validate($request, [
'id' => 'required|string',
'password' => 'required|string'
]);

if ($token = $this->guard()->attempt($credentials)) {
return $this->respondWithToken($token);
}

return response()->json(['message' => 'Unauthorized'], 401);
}

protected function respondWithToken($token) {
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => $this->guard()->factory()->getTTL() * 60
]);
}

public function guard() {
return Auth::guard();
}

Authorized Routes

Accept Header

application/json 로 설정해야 오류가 예쁘게 반환된다.

token 태우기

# header 이용한 방법
Authorization: Bearer yourtokens...

# Querystring으로도 인증 가능
https://gracefullight.github.io/me?token=yourtokens...

routes

routes/api.php
Route::group(['middleware' => 'auth:api'], function() {
Route::get('member/logout', 'MemberController@logout');
Route::get('member/me', 'MemberController@me');
});

logout

app/Http/MemberController.php
<?php
public function logout(Request $request) {
$this->guard()->logout();
return response(null, 204);
}

refresh

refresh는 auth:api 미들웨어 없이 처리되어야한다.

app/Http/MemberController.php
<?php
public function refresh() {
return $this->respondWithToken($this->guard()->refresh());
}

여담

Expired거나 Unauthoriezed경우 status code로 체크하면 된다. 5.5버전 메뉴얼이 부족하다.

· 약 1분

참조할 테이블의 PK가 increment로 정의되어 있고, 연결할 테이블의 FK가 integer로 되어있는데, SQL Syntax ERROR가 날 경우에 다음과 같이 처리하면 된다.

increment가 기본적으로 unsigned이기 때문에 외래키를 걸 컬럼이 unsigned인지 확인해보자. (컬럼 타입이 완전히 같은지 확인해보자.)

<?php
...
// FK
$table->integer('pk_id')->unsigned();

// PK
$table->increment('id');
...

여담

ALTER TABLE CONSTRAINT FORIEN KEY 구문에 문제가 있는 줄 알고 한참 삽질

· 약 1분

전체를 migration 하지 않고 부분만 migration하고 싶을 때 다음과 같이 하면 된다.

database/migrations 아래에 selected 폴더를 생성하고 옮기고 싶은 마이그레이션 파일을 넣는다.

path 옵션을 주어 selected 폴더만 migrate한다.

$ php artisan migrate --path="database/migrations/selected"

여담

파일을 직접지정해서 실행하는 방법은 없나보다.

· 약 1분

Devtools inspection is not available because it's in production mode 란 메세지와 함께 Vue 개발자 도구가 표시되지 않을 경우에 script 상단에 아래 구문을 추가한다.

Vue.config.devtools = true;

개발자 도구에 Vue 탭이 없을 때

ctrl+shift+i 키를 Vue 탭이 보일 때 까지 반복해서 눌러준다.

· 약 2분

Laravel 5.4 에서 5.5 로 업그레이드 후기

공홈을 참조해도 된다. composer.json에서 아래 패키지의 버전을 바꿔준다.

dependencies

  • laravel/framework: 5.5.*
  • phpunit/phpunit: ~6.0

dev-dependencies

  • filp/whoops: ~2.0
$ composer clearcache
$ composer update

이슈

Session, DB 문제

$ php artisan cache:clear

const UPDATE_AT 문제

const UPDATED_AT = null;처럼 timestamps 필드 중 하나를 disable 했을 때 5.5 버전에선 오류가 발생한다. 아래 처럼 모델에 setUpdatedAt 함수를 추가해주면 된다.

<?php
public function setUpdatedAt($value) {
return $this;
}

request has 문제

request->has와 같은 기능으로 동작하려면 request->filled로 바꿔줘야한다.

<?php
// 5.5에서는 name 값이 비던 안 비던 true
if ($request->has('name')) {

}

// 이게 구버전 has의 기능과 동일하다.
// name 값이 있을 경우만 true
if ($request->filled('name')) {

}

· 약 1분

가끔 가다가 인증이 안 되는 경우가 있다.

stateless

Socialite::driver('인증 타입')->stateless()->user(); 로 가져오자.

session 명 변경

config/session.phpcookie 값을 변경해준다.

session domain 변경

config/session.phpdomain 값을 null 에서 내 도메인으로 변경한다.

그리고 아래 두 명령어를 실행해주자.

$ php artisan cache:clear
$ composer dump-autoload

여담

socialite 설명에선 나오지 않았지만, Socialite 구문을 try/catch로 감싸주는게 좋았다.

<?php
try {
$user = Socialite::driver('facebook')->stateless()->user();
} catch (\Exception $e) {
return redirect()->route('login');
}

· 약 1분

Laravel DebugBar 를 이용하는게 편하지만 dump 나 json 리턴시에 DebugBar 가 보이지 않으므로 직접 찍어줘야하는 경우가 많다.

<?php
# DB 파사드를 추가한다.
use DB;
...

public function your_func(Request $request) {
// 로그를 enable 시키고
DB::enableQueryLog();
// 쿼리를 여기에 실행한다.
Member::where('조건', '값')->get();
Product::find(1);

// 쿼리 로그를 찍는다.
$queryLogs = DB::getQueryLog();
dump($queryLogs);
}

결과

배열에 query, bindings (preparedStatement 를 위한 것), time 이 상세하게 나온다.