public function articles()
{
return $this->hasMany(Article::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
Article 모델 :
protected $fillable = ['user_id', 'title', 'content'];
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Comment 모델:
protected $fillable = ['content', 'user_id', 'commentable_type', 'commentable_id', 'parent_id'];
public function commentable()
{
return $this->morphTo();
}
public function parent()
{
return $this->belongsTo(Comment::class, 'parent_id');
}
public function replies()
{
return $this->hasMany(Comment::class, 'parent_id');
}
public function article()
{
return $this->belongsTo(Article::class, 'commentable_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
엘로퀀트는 comments 테이블의 외래키를 comment_id로 자동으로 추정하기 때문에 메서드 인자에 ‘parent_id’를 전달해서 외래키를 지정했다.
마찬가지로 ‘commentable_type’, ‘commentabe_id’를 지정하지 않는 이유는 위와 같다.
‘paretn_id’ 는 null을 허용한다. 이 값이 지정되어 있지 않으면 최상위 댓글로 간주한다. ‘CommentFactory’ 에는 이 값을 비워두어 최상위 댓글을 생성하게 한다. 후에 시더에서 make 메서드에 ‘parent_id’ 를 전달해 계층적인 댓글 구조를 만들어 준다.
3.시더작성
UsersTableSeeder :
public function run()
{
factory(App\User::class, 10)->create()->each(function($user){
$user->articles()->createMany(factory(App\Article::class, 3)->make()->toArray());
});
}
CommentsTableSeeder :
public function run()
{
$faker = Faker\Factory::create();
$articles = App\Article::all();
//최상위 댓글 생성
$articles->each(function($article){
$article->comments()->createMany(factory(App\Comment::class, 3)->make()->toArray());
});
//계층적 댓글 생성
$articles->each(function($article) use ($faker){
for($i=0; $i<10; $i++)
{
$commentIds = $article->comments()->pluck('id')->toArray();
$article->comments()->create(factory(App\Comment::class)->make(
['parent_id' => $faker->randomElement($commentIds)]
)->toArray());
}
});
}
계층적 댓글 생성 부분에서 make 메서드를 보면 ‘CommentFactory’에서 지정하지 않은 ‘parent_id’값을 전달해 줌으로써 재귀적인 댓글 구조를 가질수 있게 한다.
아티즌 명령어를 통해 시더를 생성한다. 생성된 클래스는 database/seeds에 생성 된다.
php artisan make:seeder UsersTableSeeder
시더파일을 정의하자
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class UsersTableSeeder extends Seeder
{
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'@gmail.com',
'password' => bcrypt('password'),
]);
}
}
이제 시더를 실행하게되면 데이터베이스에 내용이 입력이 될 것이다.
php artisan db:seed --class=UsersTableSeeder
–class 옵션을 붙여주기 귀찮다면 DatabaseSeeder 에 시더를 등록해준다.
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(UsersTableSeeder::class);
}
}
php artisan db:seed 실행해보면 데이터가 잘 들어가는 것을 확인할 수 있다. 마이그레이션에 –seed 옵션을 사용하면 편리하다.
php artisan migrate:refresh --seed
2. Eloquent 모델팩토리 사용하기
시더에 쿼리빌더를 통해 입력하는 방법보다 모델팩토리를 생성해서 시딩하는게 편리하다. 모델팩토리 생성 커맨드를 입력하자.
php artisan make:factory UserFactory
위에서 생성한 UsersTableSeeder, UserFactory는 라라벨에 이미 생성되어 있기때문에 생성할 필요가 없다. 설명을 위해서 설명하는것 뿐이다.
이제 시더클래스에 생성한 팩토리를 사용해보자.
factory(App\User::class, 50)->create();
기존 내용을 삭제하고 시더를 사용해서 새로운 데이터를 넣어보자
php artisan migrate:refresh --seed
3. 관계모델일때 시더정의하기
일대다인 모델을 정의하자.
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function articles(){
return $this->hasMany('App\Article');
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $fillable = [
'subject', 'content',
];
public function user(){
return $this->belongsTo('App\User');
}
}
use App\Article;
use Faker\Generator as Faker;
$factory->define(Article::class, function (Faker $faker) {
return [
'subject' => $faker->title,
'content' => $faker->paragraph,
'user_id' => factory(App\User::class),
];
});
이제 UsersTableSeeder 클래스를 수정해주자.
public function run()
{
factory(App\User::class, 50)->create()->each(function($user){
$user->articles()->save(factory(App\Article::class)->make());
});
}
마이그레이션을 해보자.
php artisan migrate:refresh --seed
데이터베이스를 확인해보면 user 테이블과 aticles 테이블 관계에 따라 50개의 레코드가 생성된 것을 확인할 수 있다.
resource/views 아래에 admin 폴더를 생성한다. resource/views/auth 폴더를 복사해 resource/views/admin 아래에 붙여 넣는다. resource/views/home.blade.php 복사해 resource/views/admin 아래에 붙여 넣는다.
(3) 구현하기
Admin 컨트롤러 각각의 파일을 열어 상단에 정의 되어있는 네임스페이스 정의를 변경해준다.
namespace App\Http\Controllers\Admin;
네임스페이스를 변경했으면 각 컨트롤러의 메소드 및 사용하고 있는 트레이트 메서드를 목적에 맞게 오버라이드 한다.
로그인 기능 구현
#Admin/LoginController.php
#Auth 파사드 추가
use Illuminate\Support\Facades\Auth;
protected $redirectTo = 'admin/home';
public function __construct()
{
#미들웨어 변경, admin 파라미터 전달
$this->middleware('admin.guest:admin')->except('logout');
}
#AuthenticatesUsers 트레이트 메서드 오버라이드
public function showLoginForm()
{
return view('admin.auth.login');
}
#가드지정
protected function guard()
{
return Auth::guard('admin');
}
#admin/login.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.login') }}">
회원가입
#Admin/RegisterController.php
#User 모델대신 Admin 모델을 사용
use App\Admin;
#Auth파사드 추가
use Illuminate\Support\Facades\Auth;
protected $redirectTo = '/admin/home';
public function __construct()
{
$this->middleware('admin.guest:admin');
}
public function showRegistrationForm()
{
return view('admin.auth.register');
}
protected function validator(array $data)
{
#unique 속성에 파라미터 admins(테이블명) 전달
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return Admin::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
protected function guard()
{
return Auth::guard('admin');
}
#admin/register.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.register') }}">
#Admin/HomeController.php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('admin.auth:admin');
}
public function index()
{
return view('admin.home');
}
}
비밀번호 확인
라라벨은 특정영역에 접근하기전에 비밀번호를 확인하도록 요청할 수 있는 기능이 있다.
#Admin/ConfirmPasswordController.php
protected $redirectTo = '/admin/home';
public function __construct()
{
#미들웨어에 파라미터를 전달해 가드를 지정
$this->middleware('admin.auth:admin');
}
public function showConfirmForm()
{
return view('admin.auth.passwords.confirm');
}
라라벨은 기본적으로 이메일로 인증주소를 받아 비밀번호를 초기화 시킨다. 이메일 설정이 되어있지 않으면 메일형식을 로그로 변경해주자.
# .env
MAIL_MAILER=log
비밀번호를 초기화하는 과정은 아래와 같다.
admin/password/reset 페이지 접근
사용자 정보가 있으면 비밀번호 초기화페이지로 이동 없으면 로그인
이메일을 제출하면 해당 주소로 인증주소를 보낸다.
인증주소로 접속하여 비밀번호를 변경
이제 컨트롤러와 뷰를 수정하자
#admin/ForgotPasswordController.php
#Password 파사드 추가
use Illuminate\Support\Facades\Password;
public function __construct()
{
$this->middleware('admin.auth:admin');
}
public function showLinkRequestForm()
{
return view('admin.auth.passwords.email');
}
public function broker()
{
return Password::broker('admin');
}
#admin/auth/passwords/email.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.password.email') }}">
storage/logs/laravel.log 파일을 열어 인증주소를 확인해보면 localhost:8000/password/reset/token?email=your@email.com와 같은 형식으로 나타나는데 이 주소는 라라벨의 리셋주소이다. 따라서 Admin전용 리셋주소로 정보를 받아야 한다.
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
class AdminPasswordResetMailToken extends ResetPasswordNotification
{
use Queueable;
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
if (static::$createUrlCallback) {
$url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
} else {
$url = url(config('app.url').route('admin.password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
}
패스워드 재설정은 Illuminate\Auth\Notifications\ResetPassword의 클래스를 기본으로 사용하기 때문에 ResetPassword 을 상속받아 AdminPasswordResetMailToken를 작성해준다.
#Admin.php
use App\Notifications\AdminPasswordResetMailToken;
public function sendPasswordResetNotification($token)
{
$this->notify(new AdminPasswordResetMailToken($token));
}
모델이 사용하는 Illuminate\Foundation\Auth\User 의 클래스에 지정되어 있는 CanResetPassword 트레이트의 메서드 sendPasswordResetNotification를 오버라이드한다.
이제 이메일주소를 입력하고 인증주소를 확인해보면 원하는 주소로 변경되었음을 확인 할 수 있다.
인증메일부분을 처리했으니 인증주소를 통해 넘어온 사용자에게 비밀번호를 받고 저장하는 부분을 처리해주자.
#Admin/ResetPasswordController.php
#Request, Auth, Password 파사드 추가
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
protected $redirectTo = '/admin/home';
public function showResetForm(Request $request, $token = null)
{
return view('admin.auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
protected function guard()
{
return Auth::guard('admin');
}
public function broker()
{
return Password::broker('admin');
}
#라우트 변경
<form method="POST" action="{{ route('admin.password.update') }}">
이메일 인증하기
웹사이트를 사용하기전에 이메일을 인증해야하는 경우가 있다. 인증정보는 eamil_verified_at 필드에 저장된다.
기본인증기능에서는 라우트에 verify 옵션을 전달하면 사용할 수 있다.
Auth::routes(['verify' => true]);
커스텀한 인증에서는 비밀번호 초기화처럼 추가적으로 개발해야 한다.
#admin/VerificationController.php
use Illuminate\Http\Request;
protected $redirectTo = '/admin/home';
public function __construct()
{
$this->middleware('admin.auth:admin');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
public function show(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('admin.auth.verify');
}
#admin/auth/verify.blade.php
#라우트 변경
<form class="d-inline" method="POST" action="{{ route('admin.verification.resend') }}">
컨트롤러와 뷰쪽은 끝. 이제 인증메일을 발송하는 부분을 수정하자.
notification 클래스 생성
php artisan make:notification AdminVerifyEmail
Illuminate\Auth\Notifications\VerifyEmail 는 기본인증에 사용되는 클래스이다. Illuminate\Auth\Notifications\VerifyEmail 클래스를 상속받아 필요한 부분만 다시 정의해준다.
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;
use Illuminate\Auth\Notifications\VerifyEmail;
class AdminVerifyEmail extends VerifyEmail
{
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'admin.verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
}
생성한 AdminVerifyEmail 클래스를 Admin 모델에 적용해준다.
#Admin.php
use App\Notifications\AdminVerifyEmail;
public function sendEmailVerificationNotification()
{
$this->notify(new AdminVerifyEmail);
}
[Composer\Downloader\TransportException]
The "https://packagist.org/p/provider-2020-01%24d05e2d9aae910523f4e326c5a73b6cc7b6d498492a6f115892377153193c21bd.js
on" file could not be downloaded (HTTP/1.1 404 Not Found)
테이블간 관계를 표현할 때 -테이블 이름_열이름_foreign 예)posts_user_id_foreign -이 이름은 외래키관계를 삭제할 때 사용한다. $table->dropForeign( posts_user_id_foreign );
외래 키 열 이름 및 메서드 이름 관례 관계가 ‘일(one)’ 이 되는 쪽 모델로 접근하는 메서드는 단수 ‘다(many)’가 되는 쪽은 복수로 사용한다.
app/Post.php
public function author(){
return $this->belongsTo(User::class, 'user_id');
}
app/User.php
public function posts(){
return $this->hasMany(Post::class, 'post_id')
}
피벗 테이블 이름 및 열 이름 관례 – 다대다로 연결하려는 두 테이블의 이름을 단수로 바꾸고 알파벳 순으로 연결한다. 연결자로는 밑줄(_)을 사용한다. – 외래키의 열 이름은 모델이름_id을 사용한다.
id
post_id
tag_id
1
1
1
2
1
2
마이그레이션 생성시 – create_, make_, add_, drop_, change_ 등으로 시작하고, _table로 끝난다. 예) create_posts_table
컨트롤러 – 파스칼 표기법을 사용하여 복수형에 Controller접미사를 붙인다. 예)PostsController
이미지를 업로드하면 파라미터값(CKEditorFuncNum 등)이 넘어오지 않고 filetools-respone-error 에러를 발생시킨다.
config.filebrowserUploadMethod: ‘form’ 옵션을 지정하면 된다.
<form action="upload.php">
<textarea name="editor1" id="editor1" rows="10" cols="80">
This is my textarea to be replaced with CKEditor.
</textarea>
<script>
CKEDITOR.replace( 'editor1' , {
filebrowserUploadUrl: 'upload.php',
filebrowserUploadMethod: 'form'
});
</script>
</form>