Skip to main content

19 posts tagged with "laravel"

View All Tags

· One min read

Laravel Mix로 browserSync 옵션을 활성화 시에 Debugbar가 생기지 않는 오류가 발생할 경우 (스크립트 단에서 JSON parse 오류가 발생한다) 다음과 같이 설정해주면 된다.

해결

webpack.mix.js
mix.browserSync({
proxy: {
// artisan serve시의 주소
target: "localhost:8000",
reqHeaders: function () {
// host를 직접 지정해준다.
return {
host: "localhost:3000",
};
},
},
});

· 4 min read

웹 서버의 유저로 로그 파일이 생성되어야 하는데, 어느 순간부터 root:root 권한을 달고 daliy log 가 생성되는 경우가 있다.

여러가지 경우의 수가 있는데, 맞는 조건을 찾아서 Permission 오류가 발생하지 않게 처리해보자.

selinux

storage 에는 쓰기권한이 있어야한다.

$ chcon -R -t httpd_sys_rw_content_t storage

WebServer user

웹 서버의 유저가 다르게 설정 되어있을 때 권한이 바뀔 수 있다. 서버 설정을 열어서 유저가 제대로 설정되어 있는지 확인해보자.

nginx.conf
user nginx;

php-fpm user

php-fpm 에서 설정하는 user 와 group 이 다르게 설정 되어 있을 때 권한이 바뀔 수 있다. php-fpm 설정을 열어 유저가 제대로 설정되어 있는지 확인해보자.

php-fpm.d/www.conf
user = nginx
group = nginx

listen.owner = nginx
listen.group = nginx

log rotate

로그 파일이 너무 커지는 걸 막기위해 log rotate 설정이 되어있다면 권한이 바뀔 수 있다. logrotate 가 cron 에 물려 있는지 설정을 확인해보자.

/etc/logroate.d/*
$ pwd
/etc/logroate.d

$ vi nginx
$ vi php-fpm

cron 사용시

crontab 의 경우 root 유저로 실행이 되면 cron 에서 Laravel 을 호출할 때 log 가 root 권한으로 생성될 수 있다.

로그 분기

log 파일을 생성하는 프로세스별로 분기해서 해결할 수 있다. Stackoverflow 참조

bootstrap/app.php
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
$filename = storage_path('logs/laravel-'.php_sapi_name().'.log');
$handler = new Monolog\Handler\RotatingFileHandler($filename);
$monolog->pushHandler($handler);
});

설정을 추가해 놓으면 logs 폴더 하위에 다음과 같이 로그가 분기되어 생성된다.

storage/logs
$ ls -al .
-rw-r--r-- 1 nginx nginx 718 Aug 18 10:56 laravel-fpm-fcgi-2017-08-18.log

cron 은 root 에서 실행되나 user shell 에서 Laravel 프로세스를 실행하는 경우 root 에 의해 log 가 생성되었다면 다음과 같이 permission 을 변경해서 생성해야한다. Post 참조

bootstrap/app.php
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
$filename = storage_path('/logs/laravel-' . php_sapi_name() . '.log');
// 5번째 파라미터로 666 권한을 넘긴다.
$handler = new Monolog\Handler\RotatingFileHandler($filename, 0, \Monolog\Logger::DEBUG, true, 0666);
$monolog->pushHandler($handler);
});

RotatingFileHandler 의 Parameter 는 여기를 참조하자. 666 으로 생성 시엔 굳이 php_sapi_name()을 사용하지 않아도 된다. (rw 권한이 모두에게 있으니까)

setfacl

다른 해결 방법으로는 ACL 을 수정해 logs 폴더 자체를 해당 user:group 이 편집할 수 있게 처리하면 된다.

$ pwd
/public_html/storage/logs

$ setfacl -d -m u:nginx:rwx .

$ getfacl .
default:user:nginx:rwx

# 삭제
$ setfacl -d -x u:nginx .

여담

Laravel 프로젝트 시작시 bootstrap/app.php 안에 로그를 분기 로직을 넣고 개발하는 게 좋아보인다.

· 6 min read

댓글 테이블이 있고 이 댓글은 여러 테이블에서 사용된다고 치자. 그럼 댓글 테이블에 type과 type_id를 가져가야할 것이다. 이 때 사용할 수 있는 관계가 다형성 관계(릴레이션)인데, 공식 문서의 설명이 조금은 부족하다고 느꼈다. 파헤쳐보자.

morphTo

morphTo는 type과 type_id를 가진, 여러 테이블로 연결되어야할 테이블에서 사용하는 릴레이션 메소드이다. 공식 문서에는 데이터를 가져온 뒤 릴레이션을 연결하는 예시만 있고, Eager 로딩 (With 구문을 사용하는 방법) 후 specific한 필드를 사용하게 변경하는 경우에 대한 정보는 없다.

기본 문법

YourModel.php
<?php

class YourModel extends Model {
...

// 다형성 관계를 가질 함수를 data로 정의했다
public function data() {
return $this->morphTo();
}
}

이렇게 정의시에 YourModel::with('data')->get() 으로 호출하면 불러와져야되지만, 필드명, 모델명이 정확하지 않으면 쿼리 호출조차 되지 않는다. (심지어 에러도 발생하지 않는다)

필드명 정의

먼저 morphTo의 소스코드를 를 살펴보자.

morphTo
<?php
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $name
* @param string $type
* @param string $id
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null)

name, type, id를 파라미터로 받는다. 그럼 파라미터를 넘겨보자.

YourModel.php
<?php

class YourModel extends Model {
...

public function data() {
// morphTo의 paremeter로 null, 타입필드명, 타입인덱스 필드명을 넘긴다.
return $this->morphTo(null, 'type', 'type_idx');
}
}

여기서 name엔 도대체 뭘 넣어야 되는거야? 라고 의문이 생길 수가 있다. 함수 내에서 name 변수는 $this->getMorphs(Str::snake($name), $type, $id); 에만 딱 한 번 사용된다.

getMorphs 함수를 따라가보자.

getMorphs
<?php
protected function getMorphs($name, $type, $id) {
// $type과 $id가 명시되면 그 값을 먼저 반환한다.
return [$type ?: $name.'_type', $id ?: $name.'_id'];
}

주석처럼 typeid가 명시되면 name값은 사용되지 않는 쓰레기 값이 되어버린다. 따라서 null로 넘겨주면 된다.

타입-모델 바인딩

타입과 인덱스를 명시하면 드디어 오류메세지가 노출된다. 내가 정의한 type명을 가진 Class가 없다 라는 내용인데, 이제 타입과 모델을 연결시켜보자.

이 때 사용할 수 있는 메소드가 공식 문서에서 조금 스크롤을 내리면 있는 Custom Polymorphic Types에 잘 설명되어 있다.

하지만 등록하는 부분에 대한 설명이 You may register the morphMap in the boot function of your AppServiceProvider or create a separate service provider if you wish.라고 되어있다. 즉 AppServiceProvider에 넣던지 Service Provider로 생성이다.

한 모델에만 쓸 건데 전체에 등록을 할 필요가 없으니, 사용할 모델에 기능을 넣어보자.

YourModel.php
// Relation을 사용해야한다.
use Illuminate\Database\Eloquent\Relations\Relation;

<?php
class YourModel extends Model {
// 이 메소드는 모델이 initialize될 때 실행된다.
protected static function boot() {
parent::boot();

// 여기에 타입 별로 모델을 바인딩한다.
Relation::morphMap([
// type이 product일 경우 id는 product_id를 가리킨다.
'product' => 'App\Models\Product',
// type이 order일 경우 id는 order_id를 가리킨다.
'order' => 'App\Models\Order'
]);
}

public function data() {
// morphTo의 paremeter로 null, 타입필드명, 타입인덱스 필드명을 넘긴다.
return $this->morphTo(null, 'type', 'type_idx');
}
}

완벽해졌다. 이제 오류 없이 실행되는 것을 확인할 수 있다.

morphOne

문서 상에는 설명 되지 않은 morphOne 이란 메소드도 있다. morphMany는 관계가 설정된 값을 배열로 반환하지만 morphOne은 하나의 데이터로 반환한다. (hasOne과 hasMany처럼)

구조는 다음과 같다.

<?php
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)

morphToMany

morphedByMany

여담

다대다 다형성 관계 메소드 (morphToMany, morphedByMany)의 경우는 나중에 사용하게 되면 정리해야겠다. Relation 메소드들은 문서를 대충 훑고 API Docs를 직접 까보는게, 효율적인 것 같다.

· 2 min read

여러 데이터베이스에서 데이터를 가져와야되는 경우가 있다. (마이그레이션 또는 발송 모듈 DB의 분기 등등) 라라벨에선 아주 쉽게 설정이 가능하다.

설정

config/database.php에 새로운 커넥션 정보를 넣어주자. 새 커낵션은 mysql_new 라고 이름지었다.

config/database.php
<?php
return [
...
'connections' => [
// 기본 커넥션
'mysql' => [
...
],
// 새 친구
'mysql_new' => [
'driver' => 'mysql',
'host' => '111.111.111.111',
'port' => '3306',
'database' => 'test',
'username' => 'test',
'password' => 'test1234',
'unix_socket' => '',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
'prefix' => null,
'engine' => null
],
],
];

모델

새 커넥션에 사용할 모델을 만들어주고, 모델에서 연결할 커넥션을 설정해주자.

model.php
<?php
...
class OldMember extends Model
{
// 커넥션 변수를 다시 설정해주면 끝
protected $connection = 'mysql_new';
protected $table = 'test';
...
}

사용

기존 모델 사용법과 똑같다. 아주 간단하다.

· One min read

로그를 찍기 위해서 Log 파사드 또는 logger 헬퍼 함수를 사용할 수 있다.

두 함수의 차이점은 무엇일까?

Log::debug

  • Log 파사드가 정의되어있어야 한다. use Log;
  • NULL 값이 들어올시 NULL이 로그에 출력된다.

logger

  • 헬퍼함수라 Log 파사드 없이 사용 가능하다.
  • NULL 값이 들어올시 아무 내용도 찍히지 않는다.
  • debug 레벨이 기본이라, 다른 레벨의 로그를 찍으려면 logger()->error('에러 로그'); 처럼 함수를 하나 더 호출해야한다.
  • 단, info 레벨 로그는 info(); 헬퍼 함수를 사용할 수 있다.

· 3 min read

일본 통신사 중 소프트뱅크와 AU는 UTF8 메일이 깨져서 들어간다. (도코모는 정상적) Laravel 뿐아니라 일본으로 메일을 보내고 싶다면, 이 포스팅의 방식대로 접근하면 된다.

iso-2022-jp

먼저 인코딩을 iso-2022-jp로 바꿔줘야한다.

테스트 결과 JIS 인코딩과 같다. SJIS 인코딩도 있는데 위 인코딩에 몇가지 특수문자가 추가된 형태이다.

AppServiceProvider에 메일 인코딩을 전역으로 설정하자.

app/Providers/AppServiceProvider.php
<?php
...
use Swift;
use Swift_DependencyContainer;
use Swift_Preferences;

class AppServiceProvider extends ServiceProvider
{
...
public function register() {
// laravel의 메일 패키지는 Swift이므로 당황하지말자
Swift::init(function() {
// 헤더를 추가하고
Swift_DependencyContainer::getInstance()
->register('mime.qpheaderencoder')
->asAliasOf('mime.base64headerencoder');
// 인코딩을 변경한다.
Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
});
}
}

7bit

위키를 참조해보면 iso-2022는 문자열을 7bit 또는 8bit로 표현하는 기술이다. 근데, 소프트뱅크 문서에 7bit로 달란다. 메일 폼을 열어서 build시에 인코딩 바이트를 변경하자.

app/Mail/YourMailForm.php
<?php
...
use Swift_Mime_ContentEncoder_PlainContentEncoder;

class YourMailForm extends Mailable
{
...
public function build() {
return $this->subject('ご注文を承りました。')
->view('mail.your_mail_view')
->withSwiftMessage(function($message) {
// 전역으로 설정한 뒤에 이 메소드를 빼보고 테스트를 못 해봤다.
// 다음 분이 빼고 테스트 부탁드려요 : )
$message->setCharset('iso-2022-jp')
// 인코딩 바이트를 바꿔준다.
->setEncoder(new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit'));
});
}
}

mail.view

가장 중요한 것은 Mail View에서도 charset meta tag를 등록해줘야 한다는 것이다. 이 걸 세팅안하고 얼마나 삽질을 해댔는지, 없던 이두근이 생길지경.

resources/view/mail/your_mail_view.blade.php
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-2022-jp">
</head>
<body>
...
</body>
</html>

이렇게 세팅을 하고 메일을 보내면 정상적으로 보내지는 걸 확인할 수 있다.

여담

세 가지만 기억하자.

  • iso-2022-jp
  • 7bit
  • meta[charset="iso-2022-jp"]

· 11 min read

이전 포스팅에서 데이터를 가져왔으니, 로그인을 구현해보자.

Basic Auth

라라벨에서 제공하는 회원가입, 로그인, 비밀번호 찾기 기능을 사용하고 싶다면 artisan 명령어로 간단하게 시작할 수 있다.

$ php artisan make:auth

명령어를 실행하면 resources/views/auth 폴더에 뷰가 app/Http/Controllers/Auth에 로직이 생성되고 routes/web.php에 라우팅이 등록된다. DB도 같이 만들고 싶다면 Docs를 따라하자.

다른 테이블 사용

제공된 user 테이블을 안 쓰려면 커스터마이징이 필요하다.

설정 변경

config/auth.php 파일로 이동해 다른 모델을 등록하자.

config/auth.php
<?php
'providers' => [
'users' => [
'driver' => 'eloquent',
// 여기에 사용할 모델을 등록한다.
'model' => App\Models\Member::class,
]
]

config 안의 파일을 변경하면 config:clear를 실행해 혹시 모를 캐시를 날려주자

그리고 app/Models/Member.php로 이동해 내 모델을 라라벨 기본 인증 모듈을 사용할 수 있게 추가해야한다.

app/Models/Member.php
<?
// 라라벨 인증 사용
use Illuminate\Foundation\Auth\User as Authenticatable;
// 비밀번호 변경 메일을 위해 필요한 trait
use Illuminate\Notifications\Notifiable;

// Authenticatable 를 상속받는다.
class Member extends Authenticatable {
// Notifiable trait를 추가한다.
use Notifiable;

protected $guarded = [
'member_id', 'remember_token'
];

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

인증 필드 변경

Basic Auth는 기본 필드를 email로 잡고 있기 때문에 id 필드를 사용하게 변경해야한다.

app/Http/Controllers/Auth/LoginController.php
<?php
// username 메소드를 추가
public function username() {
// 반환할 필드를 선언한다.
return 'id';
}

회원가입 후 자동로그인

회원가입이 성공하면 세션을 생성해줘야한다.

app/Http/Controllers/Auth/RegisterController.php
<?php
// registered 메소드를 Override
protected function registered(Request $request, $user) {
// 세션 생성
Auth::attempt([
'id' => $request->input('id'),
'password' => $request->input('password')
]);

return response(null, 204);
}

ajax 처리

로그인

로그인을 ajax로 처리해야될 경우 커스터마이징이 필요하다.

app/Http/Controllers/Auth/LoginController.php
<?php
// authenticated 메소드를 Override
protected function authenticated(Request $request, $user) {
// 인증이 된 경우 페이지 전환이 아닌 전환될 페이지를 json으로 반환한다.
if ($request->ajax()) {
return response()->json([
'href' => url()->previous()
]);

} else {
return abort(405);
}
}

비밀번호 찾기

비밀번호 찾기를 ajax로 처리해야될 경우 커스터마이징이 필요하다.

app/Http/Controllers/Auth/ForgotPasswordController.php
<?php
public function sendResetLinkEmail(Request $request) {
$this->validateId($request);
// email이 아닌 아이디로 검색을 해 메일을 찾고 싶다면
$data = Member::where('id', $request->only('id'))
->first(['email']);

$email = isset($data->email) ? $data->email : null;

// 여기서 email이 User Model(Member Model)에 바인딩된다.
// 아래의 sendPasswordResetNotification 메소드에서 $this->email 값이다.
$response = $this->broker()->sendResetLink([
'email' => $email
]);

// 리턴 값을 변경해주자.
return response()->json([
'email' => $email
], $response == Password::RESET_LINK_SENT ? 200 : 500 );
}

routing 예외

ajax 요청으로 바꿨다면 굳이 필요없는 기본 route는 등록할 필요가 없다. (예를 들면 로그인 페이지) 먼저 routes/web.php에서 Auth::routes(); 를 지워주고 라라벨 route 파일을 열어보자.

vendor/laravel/Illuminate/Routing/Router.php
// 994 라인
public function auth()
{
// Authentication Routes...
$this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController@login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');

// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController@reset');
}

입맛에 맞게 web.php로 가져와 사용하자.

notification 예외

Noticifation은 사용자에게 빠르게 알림을 보낼 수 있는 기능이지만, 정해져 있는 템플릿을 사용하므로 커스터마이징이 되게 힘들다. 비밀번호 찾기시에 보낼 메일을 정해진 템플릿을 사용할 수 없다면 메소드를 수정하자.

app/Models/Member.php
<?php
// 이 메소드를 override해야한다.
// 첫 파라미터는 비밀번호 인증용 token이 들어온다.
public function sendPasswordResetNotification($token) {
Mail::to($this->email)->send(new PasswordReset($token));
}

Auth

Basic Auth를 사용하는데 건드려야 되는 곳이 많으므로 Auth 모듈만 사용하는게 정신건강에 좋다고 본다. (Bootstrap 기반의 Laravel에 딱 맞는 모양을 입은 프로젝트라면 기본 인증이 좋겠지만)

로그인

먼저 사용할 모델에 Authenticatable 클래스를 상속 받자 그리고 LoginController에서 Auth::attempt() 메소드를 실행하면 끝이다.

app/Http/Controllers/LoginController.php
public function login(Request $request) {
$password = $request->password;

if(Auth::attempt([ 'id' => $request->id, 'password' => $password ])){
return response(null, 200);

}else{
return abort(401);
}
}

not bcrypt

Auth 모듈은 기본으로 bcrypt를 사용해 비밀번호를 암호화하고 비교하는데 다른 암호화 방식을 사용해야하는 경우가 있다. bcrypt를 사용하지 않게 처리해보자.

app/Models/Member.php
// 이 메소드를 override 해야한다.
public function getAuthPassword() {
// bcrypt 비교를 하지 않기 위해 강제로 해시를 생성한다.
return Hash::make($this->password);
}

이제 Auth::attempt() 메소드에 패스워드를 넘길 때 암호화를 해주고 넘기면 된다.

N개의 세션

관리자와 회원은 같은 세션을 사용하면 안 된다. 세션을 분기해보자.

모델 생성

먼저 모델을 하나 만들고 Authenticatable 클래스를 상속받는다.

app/Models/Admin.php
<?php

use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable {
// 속성은 테이블에 맞게 알아서 정리하자
}

설정 추가

만든 모델을 Laravel Auth에서 사용한다고 등록을 해줘야한다.

config/auth.php
<?php
return [
'guards' => [
...
// 아래에 등록된 provider를 admin이란 이름의 guard로 사용
'admin' => [
'driver' => 'session',
'provider' => 'admin'
]
],
'providers' => [
...
// 방금 만든 모델을 Auth의 Provider로 등록
'admin' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class
]
],
];

passowrds 속성은 제공되는 password_resets 기능을 사용할 경우에만 추가해주면 된다.

미들웨어

관리자 세션이 인증된 사람만 관리자 페이지에 접근할 수 있어야한다. php artisan make:middleware Admin 명령어를 실행해 Admin Middleware를 만들자.

app/Http/Middleware/Admin.php
<?php
use Auth;
use Closure;

class Admin {

public function handle($request, Closure $next) {
// admin session이 있는 경우만 request를 진행한다.
if (Auth::guard('admin')->check()) {
return $next($request);
}

// 아닐경우 관리자 로그인 페이지로 넘긴다.
return redirect()->route('admin.login');
}
}

그리고 Http Kernel에 방금 만든 Admin Middleware를 라우팅에서 사용할 수 있게 등록해준다.

app/Http/Kernel.php
<?php
...
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
// admin 이름으로 Admin Middleware를 호출할 수 있게 등록
'admin' => \App\Http\Middleware\Admin::class,
];

새로운 라우팅 파일로 관리하기 위해 RouteServiceProvider에 설정을 추가한다.

app/Providers/RouteServiceProvider.php
<?php
...
public function map() {
...
$this->mapAdminRoutes();
}

protected function mapAdminRoutes() {
// /admin 으로 들어오는 요청에 대해 처리한다.
Route::prefix('admin')
// 이런식으로 한 번에 추가할 수도 있다. 하지만 except 함수를 쓸 수 없는 것 같고,
// 라우팅 페이지에서 미들웨어를 등록해주는 게 더 편했다.
// ->middleware(['web', 'admin'])
->middleware('web')
->namespace($this->namespace)
// routes/admin.php 파일을 관리자 라우팅 파일로 등록한다.
->group(base_path('routes/admin.php'));
}

마지막으로 라우팅 파일에 미들웨어를 넣어준다.

routes/admin.php
<?php
// admin middleware를 사용하고, namespace에 admin.을 추가한다.
Route::group(['middleware' => 'admin', 'as' => 'admin.'], function() {
// 이 라우팅은 route('admin.main') 으로 접근이 가능해진다.
Route::get('/', 'AdminController@index')->name('main');
});

활용

기존 Auth 메소드들에 guard만 추가해주면 쉽게 사용 가능하다.

<?php
Auth::guard('admin')->attepmt();
Auth::guard('admin')->check();
Auth::guard('admin')->user();
Auth::guard('admin')->logout();

로그아웃

Auth::logout() 메소드를 호출하면 된다.

이슈

세션 아이디

라라벨에선 로그인을 할 때 세션아이디를 새로운 값으로 엎어쳐버린다. 따라서 같은 환경에서 접근했는데, 로그인을 하면 다른 사용자가 되는 경우가 생긴다. (로그아웃시에는 그대로다.)

<?php
// 로그인 전에 세션아이디를 가져와
$old_session_id = Session::getId();
// 로그인이 성공하면 DB에서 등록된 세션값을 업데이트하자.

소셜 로그인

laravel/socialite 패키지를 설치하고 아주 쉽게 연동이 가능하다.

세팅

$ composer require laravel/socialite

기본 socialite 패키지는 facebook, twitter, linkedin, google, github or bitbucket 만 연동이 가능하므로 다른 소셜 로그인을 연동하고 싶을 경우 Socialite Providers 패키지를 사용하면 된다.

Socialite Providers 사이트에는 Naver, Kakao, BattleNet(?) 등의 소셜 연동이 가능한 패키지가 무수히 많다.

여담

JWT 인증과 같이본다면 Laravel을 사용한 실무에서 필요한 인증은 다 마무리한 셈일 듯

· 17 min read

Eloquent ORM을 사용해 데이터를 가져와보자. 이전 포스팅에서 이어진다. (Eloquent ORM 기능만 필요하다면 3.Model부터 보면 된다)

Migration

DBMS에 상관 없이 테이블을 똑같이 생성하기 위한 기능이다.

생성

artisan 명령어로 간단히 생성할 수 있다. 날짜_테이블 형식의 파일이 database/migrations 밑에 생성된다. 이 파일들의 자동 생성된 이름을 절대 변경하지 말자 내부적으로 언더스코어를 explode해 class를 호출하기 때문에 건들면 고생한다.

# 모델을 생성하면서 같이 생성
# m 옵션을 추가한다.
$ php artisan make:model -m 모델명

# 마이그레이션만 생성
$ php artisan make:migration 마이그레이션명

생성 후에 up 메소드 안에 테이블을 꾸며주고 down 메소드에 테이블을 지울 때 실행할 로직을 구현한다. 컬럼 타입을 지정하는 메소드는 여기를 참조하면 된다.

<?php
public function up() {
Schema::create('table_name', function (Blueprint $table) {
$table->increments('idx');
$table->string('id', 20)->unique();
$table->string('name', 30);
$table->string('email', 100)->unique();
$table->string('password', 250);
$table->tinyInteger('tiny')->nullable();
$table->timestamps();
});
}

// 테이블에 외래키 제약조건이 걸려있을 경우 migration으로 일괄 삭제시에
// 오류가 발생하므로, 외래키 체크 옵션을 비활성화 해줘야될 수 도 있다.
public function down() {
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
Schema::dropIfExists('table_name');
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
}

실행

php artisan migrate 명령어로 실행하면 된다.

  • migrate => 전체 실행
  • migrate:refresh => 다시 실행
  • migrate:rollback => 마지막 migrate 시점으로 돌림
  • migrate:reset => 제거

Tinker

이미 데이터가 들어간 테이블이 있어 migrate --path로 한 파일만을 실행해야 하는데, tinker를 사용하면 더 쉽게 해당 마이그레이션을 실행시킬 수 있다. Tinker는 커맨드로 라라벨 쉘(실행환경)으로 들어간다고 생각하면 된다.

# tinker
$ php artisan tinker

# shell로 접속된다.
Psy Shell v0.8.5 (PHP 7.0.7 ??cli) by Justin Hileman
New version is available (current: v0.8.5, latest: v0.8.8)
>>>
# blueprint 의존성을 추가해주고 (Blueprint는 up 메소드에 DI로 들어가 있기에 읽지 못한다.)
$ use Illuminate\Database\Schema\Blueprint;
# 새로 실행하려 했던 스키마를 실행시켜주기만 하면 된다.
$ Schema::create('new_table', function (Blueprint $table) {
$table->increments('idx');
$table->string('id', 20)->unique();
$table->timestamps();
});

DB에 해당 테이블이 생성된 걸 확인할 수 있다.

Seed

Seed는 테이블에 필수 데이터 또는 더미 데이터를 심어주는 과정이다. 테스트에 필요한 데이터를 넣어주는데 아주 효과적이다.

생성

$ php artisan make:seeder 시더명

database/seeds 아래에 파일이 생성된다. 파일을 열어보면 run() 메소드 하나가 있는데, 여기에 시드파일이 호출될 때 실행할 로직을 구현해주면 된다.

database/seeds/BoardSeed.php
<?php
public function run() {
// 모델로 생성하기
Board::create([
'title' => '공지',
'author' => '관리자',
'content' => '공지입니다'
]);
// DB 파사드로 생성하기
DB::table('board')->insert([
'title' => '공지',
'author' => '관리자',
'content' => '공지입니다'
]);
}

Faker

랜덤한 테스트용 데이터가 많이 필요하다면, 가짜(더미) 데이터를 생성해주는 라이브러리를 사용할 수 있다. 링크를 따라가보면 정말 엄청난 종류의 랜덤 데이터를 생성할 수 있음에 놀랄 것이다.

faker를 사용하려면 ModelFactory를 먼저 정의해야한다. 상품 모델을 가져와 가짜 데이터 타입을 정의해보자.

database/factories/ModelFactory.php
<?php
$factory->define(App\Models\Product::class, function(Faker\Generator $faker){
return [
// 메소드는 위의 라이브러리를 참고하자.
// 상품명을 중복되지 않게 city(도시명)으로 가져온다.
'name' => $faker->unique()->city,
// 색상 헥스코드를 가져온다.
'color' => $faker->safeColorName,
// 1000~50000원 사이의 가격을 가져온다.
'price' => $faker->numberBetween(1000, 50000),
// lorempixel의 랜덤 이미지를 가져온다.
'thumbnail' => $faker->imageUrl(200, 100),
'detail_image' => $faker->imageUrl(300, 600),
'qty' => $faker->numberBetween(1, 1000),
'status' => 1,
'owner' => $faker->name,
'sorting' => $faker->numberBetween(1, 9999)
];
});

실행

위에서 선언한 faker 모델을 seed에서 호출해보자.

database/seeds/ProductSeed.php
<?php
public function run() {
// 두번째 인자로 실행시킬 횟수를 넣어주면 된다.
factory(App\Models\Product::class, 100)->create();
}
$ php artisan db:seed

테이블에 상품 더미데이터가 100개 생성된 것을 확인할 수 있다.

Model

모델은 하나의 테이블의 타입을 정해 놓은 것이라 보면 된다. 데이터를 가져오거나 넣을 때 모델에 정의된 형태로 가져온다.

기본 구조

아래 구조 정도만 알아두면 된다. 더 자세한건 Eloquent Model Class를 확인해보자.

app/Models/Board.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Board extends Model {
// 임의의 생성 필드가 있다면 설정해준다. (기본값 created_at)
const CREATED_AT = 'created_dt';
// 사용하지 않을 경우 Null로 초기화해주자. (기본값 updated_at)
const UPDATED_AT = null;
// 둘 다 사용하지 않을 경우 timestamps만 꺼주면 된다. (기본값 true)
public $timestamps = false;
// PK가 auto increment가 아닐경우 false로 바꿔준다. (기본값 true)
public $incrementing = false;

// PK 값을 변경한다. (기본값 id)
protected $primaryKey = 'idx';
// 테이블 명을 변경한다. (기본값 모델명의 복수형)
protected $table = 'board';

// dump insert 및 dump update가 안되는 필드를 설정한다.
protected $guarded = [
'idx'
];

// guarded와 fillable 중 하나만 있으면 된다. (써보니 guarded가 편하더라.)
// dump insert 및 dump update가 가능한 필드를 설정한다.
//protected $fillable = [
// 'title', 'content'
//];

// 노출시키지 않을 필드가 있을 경우
protected $hidden = [
'password'
];
}

Relationship

1:1

hasOne과 belongsTo로 연결한다.

1:n

hasMany와 belongsTo로 연결한다.

n:m

belongsToMany로 연결한다. 처음에 제일 감이 안왔던 Relationship이지만 코드를 보면 이해하기가 쉽다. 주문(Order) - 주문 상품(OrderProduct) - 상품(Product) 테이블이 있다고 가정한다. 각각 시작 - 링크(피벗) - 종료 테이블이다.

<?php App/Models/Order.php

public function product(){
// 마지막으로 연결될 모델명, 링크 테이블 명, 시작->링크를 연결시킬 인덱스, 링크->종료를 연결시킬 인덱스
return $this->belongsToMany('App\Models\Product', 'order_product', 'order_id', 'product_id')
// 링크 테이블의 컬럼을 가져와야할 때
->withPivot(['price', 'quantity']);
}

이렇게 정의하면 뷰에서 order->productforeach로 돌려 product->product_id처럼 접근할 수 있고 product->pivot->price로 pivot 테이블의 데이터도 가져올 수 있다.

1:1:1

이런 관계가 있을 경우에 라라벨에서 관계메소드를 직접 지원하지는 않는다. 억지로 사용하려면, hasOne을 두 번 사용해 연결해야하는데, Select Query를 두 번 날려야 된다는 소리다. (참을 수 없다)

BelongsToThrough trait 패키지를 통해 깔끔하게 해결할 수 있다. 메소드에서 belongsToMany처럼 커스텀 외래키를 사용해야할 경우엔 5번째 파라미터로 [ 클래스 => 키 ] 형식으로 넘겨주면 된다. (여기의 소스를 참고하자)

이슈

ORM의 Join 방식

한 방 쿼리가 불가능하다. 오직 PK를 통해 Select된 데이터들을 다시 PHP 단에서 합쳐서 보여준다. 한 방이 필요할 때는 Database에서 View를 이용하거나 DB 파사드를 써야한다.

Composite Key

라라벨 Eloquent Model에서 복합키를 사용할 수는 없다. 편법으로 trait를 추가할 수 있는데 find 메소드도 overriding 해야 된다.

Timestamps

created_at과 updated_at은 timestamps 형식이라 date_format과 같은 쿼리함수 대신 whereDate 메소드로 연산을 실행해야한다. 물론 DB에 데이터가 들어가기 전 datetime으로 해당 컬럼을 변경해주면 된다. (timestamps의 유효기간은 2035년까지므로.)

Group By

GroupBy를 이용한 쿼리 사용시에 Syntax error or access violation: 1055 'table.column' isn't in GROUP BY라는 오류 메세지가 보이며 실행이 안 되는 경우가 있다. 해당 컬럼으로 GroupBy를 하지 않았는데도 발생한다.

database config의 strict 모드 중 ONLY_FULL_GROUP_BY 모드가 활성화 되어있어서 그런데 이 모드만 제외를 시켜주면 된다.

config/database.php
<?php
'connections' => [
'mysql' => [
// strict 속성을 false로 바꿔준다.
'strict' => false,
// modes 속성을 추가해 기본 strict 옵션을 다시 추가한다.
'modes' => [
'STRICT_TRANS_TABLES'
'NO_ZERO_IN_DATE'
'NO_ZERO_DATE'
'ERROR_FOR_DIVISION_BY_ZERO'
'NO_AUTO_CREATE_USER'
'NO_ENGINE_SUBSTITUTION'
]
],

변경 후 config:cache로 config 파일들을 다시 캐싱해주자.

연동

만든 모델을 선언만 하면 Eloquent Model 메소드Query Builder 메소드를 사용할 수 있다.

CRUD는 다음과 같다.

YourController.php
<?php

use App\Model;

public function test($id) {
// PK로 데이터 1개 반환
Model::find($id);
Model::where('idx', $id)->first();

// 전체 데이터 반환
Model::all();
// 조건 데이터 반환
Model::where()->get();

// Insert
$model = new Model;
$model->column = '추가 값';
$model->column2 = '추가 값2';
$model->save();

// Mass Insert
Model::create([
'column' => '추가 값',
'column2' => '추가 값2'
]);

// Bulk Insert
Model::insert([[
'column' => '추가 값',
'column2' => '추가 값2'
], [
'column' => '추가 값',
'column2' => '추가 값2'
]]);

// Update
$model = Model::find($id);
$model->column = '변경 값';
$model->column2 = '변경 값2';
$model->save();

// Mass Update
Model::where()->update([
'column' => '변경 값',
'column2' => '변경 값2'
]);

// Delete
Model::destory($id);
Model::find($id)->delete();
Model::where()->delete();
}

Collection과 Model의 차이

Model에서 쓸 수 있는 메소드와 Collection에서 쓸 수 있는 메소드가 다르다. Model Class에서 정의하는 관계 메소드들은 Model에서만 사용 가능하다.

  • Collection: Array<Model> 즉 모델의 집합이다.
  • Model: 테이블에서 하나의 행이라고 생각하자.

Paging

모델에서 paginate 메소드를 사용하면, querystring에 page 변수가 붙어 페이징이 된다. (예를들면 /boards?page=1) 라라벨의 기본 페이징은 Bootstrap의 Class를 사용한다.

<?php
// 이렇게 모델을 가지고 왔다면
Model::where()->get();

// get을 paginate로 바꿔주기만 하면 된다. (뿌려지는 list item의 기본값은 15이다.)
Model::where()->paginate(10);

// next와 prev 버튼이 없는 pagination을 구현하고 싶다면
Model::where()->simplePaginate(10);

페이징 뷰를 꾸미고 싶다면 아래 명령어를 실행해 resources/views/vendor/pagination에 view가 생기고 default.blade.php를 수정하면 된다.

$ php artisan vendor:publish --tag=laravel-pagination

ajax pagination

실무에선 Paging 호출을 GET보단 AJAX를 쓰는게 깔끔한데 List View와 ListItem View, Controller 세부분을 모두 변경해줘야한다. 아래 소스는 jQuery를 사용하고 있다고 가정한다. (Frontend Framework를 같이 사용하고 있다면 더 깔끔하게 처리 될 수 있을듯)

Controller

YourController.php
<?php
public function list(Request $request) {
// ajax 요청일 경우에 listitem 뷰 반환
if ($request->ajax()) {
return view('listitem', [
'data' => Model::where()->paginate(10);
]);
// get 요청일 경우에 list 뷰 반환
} else {
return view('list', [
'data' => Model::where()->paginate(10);
])
}
}

List view

list.blade.php
<div id="list">
@include('listitem')
</div>
<script>
var paging_listener = function () {
// 페이지 버튼에 click listener 등록
$('.pagination a').click(function (e) {
// a href 로 페이지가 이동하는걸 방지한다.
e.preventDefault();
get_list($(this).attr('href'));
});
};

var get_list = function (url) {
$.get(url)
.then(function(html) {
$('#list').html(html);
// 페이지가 다시 그려졌으므로 listener를 다시 등록한다.
paging_listener();
});
};

$(function () {
paging_listener();
});
</script>

ListItem view

listitem.blade.php
<ul>
@foreach($data as $item)
<li>{{ $item->name }}</li>
@endforeach
</ul>
// pagination html이 반환되어 자동으로 보여진다.
{{ $data->links() }}

multiple pagination

한 페이지에 여러 리스트가 있는 경우가 종종 있다. 먼저 paginate 메소드를 살펴보자.

<?php
/**
* Paginate the given query into a simple paginator.
*
* @param int $perPage
* @param array $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null)
  • perPage: 한 페이지에 표시될 Item 수
  • columns: 가져올 컬럼명의 배열
  • pageName: page 변수명 (예를들면 /boards?page=1 에서 page 변수명을 변경 가능)
  • page: 가져올 페이지

Controller

Controller에서 paginate 메소드를 활용해보자.

YourController.php
<?php
public function list(Request $request) {
if ($request->ajax()) {
// list1_page 값이 들어올 경우
if ($request->has('list1_page')) {
// list1_item 뷰에 바인딩해준다.
return view('list1_item', [
'data1' => $this->get_list1()
]);
} else if ($request->has('list2_page')) {
return view('list2_item', [
'data2' => $this->get_list2()
]);
}
}

// 메인 요청
$data1 = $this->get_list1();
$data2 = $this->get_list2();
return view('list', [
'data1' => $data1,
'data2' => $data2
]);
}

protected function get_list1() {
return Model::where()->orderBy()->paginate(5, ['*'], 'list1_page');
}

protected function get_list2() {
return Model::where()->paginate(10, ['*'], 'list2_page');
}

List view

위의 script에서 조금만 수정해주면 된다. (list item view는 위와 동일한 코드의 반복이므로 생략)

list.blade.php
<div id="list1">
@include('list1_item')
</div>
<div id="list2">
@include('list2_item')
</div>

<script>
var paging_listener = function () {
$('.pagination a').click(function (e) {
e.preventDefault();
var page = $(this).attr('href').split('?')[1];
// page.split('=')[0]은 페이지 변수명을 가져온다.
var paging_type = page.split('=')[0] === 'list1_page'
? 'list1'
: 'list2';
get_list($(this).attr('href'), paging_type);
});
};

var get_list = function (url, target) {
$.get(url)
.then(function(html) {
$(`#${target}`).html(html);
paging_listener();
});
};
</script>

여담

이제 DB에서 데이터를 가져오는 것까지 끝났다. 다음 포스팅에서는 User Login을 구현해보자.

· 11 min read

image from hexo

1등이 된지가 14년 7월이다. 이 글은 17년 글이다. 다른 장점이 궁금하다면 다른 포스트나 stackoverflow에서 해결해줄 것 같다.

이 글을 읽어주시는 분들은 Modern PHP에 대해 이해하고 있다고 생각했기 떄문에 문법적인 것들은 설명하지 않을 것이다. 더군다나 라라벨을 도입하면서 삽질한 내용을 정리하고 있기에, 입문용은 절대 아니다. 기본적으로 아래와 같은 PHP 개념을 알고 있어야한다.

  • PSR
  • Composer
  • Class
  • Namespace
  • Closure
  • Trait
  • PDO

추가적으로 이런 걸 사용해 봤다면 더 쉬울 듯하다.

  • Monolog
  • 다른 MVC 프레임워크
  • Lodash

설치

PHP

PHP 7 이상을 설치해주고, 아래 Extension은 웬만하면 열어주는 것들이니 크게 신경쓰지 말자.

  • PHP >= 7.0.0
  • OpenSSL PHP Extension
  • PDO PHP Extension
  • Mbstring PHP Extension
  • Tokenizer PHP Extension
  • XML PHP Extension

Composer

Composer 홈페이지에서 다운받고 전역설정만 해주면 된다.

Laravel Cli

Laravel 명령어를 사용할 수 있게 composer로 전역 설치하자.

$ composer global require "laravel/installer"

프로젝트 생성

초기 생성

$ laravel new 프로젝트명

위 명령어를 실행하면, 알아서 composer 패키지까지 설치된다.

clone 했을 때

환경 설정 파일과 프로젝트 키가 없으므로 적절하게 세팅해줘야한다.

# 패키지 의존성 설치
$ composer install

# .env 파일 복사 (설정파일 생성)
# post-root-package-install scripts로 들어가 있긴한데, 복사가 안 되는 경우
$ cp .env.example .env

# 프로젝트 키 생성
$ php artisan key:generate

실행

프로젝트를 실행해보자.

$ php artisan serve

이제 localhost:8000으로 라라벨 프로젝트에 접근이 가능하다.

php artisan 명령어는 laravel에서 사용하는 커맨드 쉘이다. php artisan로 명령어 리스트가 나오고 php artisan help 명령어 로 해당 명령어의 옵션을 볼 수 있다.

프로젝트 구조

라라벨 한글 메뉴얼이나 공식 메뉴얼을 보면 되는데 마치 사전을 펴놓았는데 어떤 단어를 찾는지 까먹은 느낌이 들 것이다.

모든 프레임워크는 Model, View, Controller, Routing만 알면 끝이라는 것을 잠시 기억 속에서 꺼내보고 딱 3가지 경로만 기억하자.

  • Route: routes/
  • Controller: app/Http/Controllers/
  • View : resources/views/

모델은 어딨어? 라고 생각할 것 같은데 Model을 완벽하게 사용하려면 ORM을 써야하고 DB를 ORM에 맞게 정규화해야하며 이 작업은 처음 진행하기에 고통스럽다. 다음 포스트에 진행해보자.

Route

모든 웹 프레임워크의 기본은 Routing이다. routes 폴더에 기본으로 api, channels, console, web 파일이 보이는데 그 중 2가지만 알면 된다.

  • api.php: Token 인증이 필요한 라우터로 /api/{route} 로 접근이 가능하다.
  • web.php: 기본적인 Route이다. GET이 아닌 다른 메소드는 CSRF Token이 있어야만 호출이 가능하다.

Controller

Routing 요청을 처리하는 로직이 들어있는 부분이다.

View

Controller에서 처리된 데이터를 플랫폼에 보여주는 부분이다.

프로젝트 세팅

App 설정

env 파일

.env 파일에서 APP_NAME과 DB로 시작하는 설정을 바꿔준다.

app 파일

config/app.php
<?php
return [
// 시간대 세팅을 한다.
'timezone' => 'Asia/Seoul',
// 언어 설정을 한다.
// fallback_locale은 언어팩이있어야 하므로 아래에서 진행할 laravel-lang 설치 후 바꿔주자.
'locale' => 'ko',
]

database 파일

config/database.php
<?php
return [
'connections' => [
'mysql' => [
// mysql일 경우 utf8로 설정해준다 (maria는 utf8mb4)
// DB 설정과 다르게 Model 이용시 charset 및 collation이 지정되어 들어가기에 꼭 설정해줘야한다.
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
// 내 테이블의 접두사가 필요하다면 여기서 설정한다.
// 예를들어 test_member, test_product 이런식의 테이블 명명규칙이라면 아래처럼 주면된다.
'prefix' => 'test_',
// engine 옵션은 my.ini에서 만져주자..
'engine' => null,
],
];

config 내의 파일과 .env파일을 건드릴 경우 laravel 환경을 다시 캐싱해주거나 아예 삭제해야 적용이 된다. php artisan config:cache 명령어 또는 php artisan config:clear 명령어를 실행해주자.

Debug Bar

정말 멋진 디버깅 툴이다. 간단히 설치 후에 .env 파일의 APP_DEBUG 옵션이 true이면 자동으로 View 하단에 생성된다. 아래와 같은 데이터를 확인할 수 있다.

  • 로그 내역
  • 뷰 데이터 바인딩 확인
  • 오류
  • 라우팅
  • 쿼리 실행
  • 메일 발송 데이터
  • 실행 시간
  • Request

설치

$ composer require barryvdh/laravel-debugbar

laravel-lang

기본 오류 메세지들의 localization 패키지이다.

설치

$ composer require caouecs/laravel-lang:~3.0

적용

vendor/caouecs/laravel-lang/src/{국가} 폴더를 resources/lang/{국가} 로 복사하고 config/app.phpfallback_locale을 {국가}로 바꿔주면 된다.

# 귀찮으니 git bash에서 날려봅시다.
$ cp -r ./vendor/caouecs/laravel-lang/src/ko ./resources/lang/ko

라우팅

routes/web.php
<?php
// url 변수
Route::get('/your_url/{id}', function($id) {
return $id;
});

Route::get('/your_url/{id?}', function($id = null) {
return $id;
});

컨트롤러

생성

artisan 명령어로 간단히 생성할 수 있다.

# make:controller 폴더명/컨트롤러명
$ php artisan make:controller TestController

# 리소스 메소드를 같이 생성
$ php artisan make:controller TestController --resource

연결

routes/web.php
<?php
// 컨트롤러명@메소드명 으로 바로 연결시킬 수 있다.
Route::get('/url/{id}', 'TestController@get');
Route::post('/url', 'TestContoller@post');

// CRUD가 명확한 컨트롤러인 경우 Resource를 사용하는 게 편할 수 있다.
Route::resource('tests', 'TestController');
// only 또는 except로 원하는 resource만 가져갈 수 있다.
Route::resource('tests', 'TestController', [
'only' => [
'index', 'show'
]
]);
app/Http/Controllers/TestController.php
<?php
use Illuminate\Http\Request;

class TestController extends Controller{

public function get($id){

// 뷰를 호출
return view('test.detail', [
'data' => $data
]);
}

public function post(Request $request, $id){
// Request 데이터를 제어할 수 있다.
$input = $request->all();
$name = $request->input('name');

$data = [];

// json body와 201 http code를 반환
return response()->json([
'data' => $data
], 201);
}
}

Resource

리소스 사용시라면 아래와 같은 메소드로 구성되어야한다. 게시판 컨트롤러라면 아래와 같은 구성일 것이다.

메소드경로액션라우트.메소드
GET/boards게시판 메인boards.index
GET/boards/create게시글 생성페이지boards.create
POST/boards게시글 저장boards.store
GET/boards/{id}게시글 상세페이지boards.show
GET/boards/{id}/edit게시글 수정페이지boards.edit
PUT/PATCH/boards/{id}게시글 수정로직boards.update
DELETE/boards/{id}게시글 삭제boards.destroy

블레이드 템플릿을 사용하며 홈페이지 설명이 꽤나 자세하다. 당장 알아야 할건 아래 두 개 정도 뿐이다. 나머지는 그때 그때 문서를 참조하자.

resources/views/test/detail.blade.php
// 바인딩한 데이터를 {{ }} 구문으로 바로 접근할 수 있다.
{{ $data->name }}님의 정보입니다.
resources/views/test/create.blade.php
// post로 전송시
<form method="post" url="/url">
// post로 데이터를 전송시 csrf 토큰이 필요하다.
// 이 헬퍼함수를 호출하면 자동으로 토큰 값이 들어간 input hidden 필드가 생성된다.
{{ csrf_field() }}
이름: <input type="text" name="name" vavlue="">
</form>

// 기타 메소드로 전송시
<form method="post" url="/url">
{{ crsf_field() }}
// PUT, PATCH, DELETE 와 같은 메소드로 전송시 form 태그에서는 지원을 해주지 않으므로 method spoofing이 필요하다.
// 이 헬퍼함수를 호출하면 해결된다.
{{ method_field('PUT')}}
</form>

여담

이제 라라벨을 이용해 데이터를 서버에 보내고 뷰를 구성할 수 있게 되었다. 다음 포스팅에서는 DB 연동과 ORM 모델을 사용해 데이터를 가져오고 페이징 처리를 해보자.