[laravel] This cache store does not support tagging 해결방법

태그가 지정된 캐시를 저장하고자 할때 ‘This cache store does not support tagging.’ 라는 에러가 발생했다.

코드 :

$articles = Cache::tags('article')->remember.env($cacheKey, now()->addMinutes(30), function () {
            return Article::latest()->paginate(10);
        });

문제해결:

캐시드라이버가 file 또는 database 에서는 태그기능을 지원하지 않는다.
.env 에서 CACHE_DRIVER값을 redis 또는 memcache또는 array로 변경한다.

#CACHE_DRIVER=file
CACHE_DRIVER=array

설정캐시를 날려 수정한 사항이 반영되도록 한다.

php artisan config:cache

만약 드라이버를 수정하지 않고 특정 위치에서만 사용하고 싶을때는 Cache파사드의 store메서드에 드라이버를 인자로 전달하면 된다.

        $articles = Cache::store('array')->tags('article')->remember($cacheKey, now()->addMinutes(30), function () {
            return Article::latest()->paginate(10);
        });

[apache2] a2enmod rewrite

환경

  • 우분투 18.04.2 LTS
  • Apache/2.4.29 (Ubuntu)

XE3에서 index.php에는 접근되는데 다른페이지는 404에러가 발생했다. 짧은 주소를 이용하는게 원인인가 싶어 서버설정을 찾아봤다.

apache2서버에서 짧은 주소를 사용하고 싶은경우 mod_rewrite모듈을 활성화 시켜야 한다.

sudo a2enmod rewrite

그리고 /etc/apache2/apache2.conf 에서 ‘AllowOverride all’ 로 변경한다.

<Directory /your/path>
        Options Indexes FollowSymLinks
        AllowOverride all
        Require all granted
</Directory>

아파치를 재시작해준다.

service apache2 restart

그래도 해결이 안되는 경우는 대부분 .htaccess 문제이니 파일을 확인해서 모듈 및 규칙이 올바르게 설정되어 있는지 확인한다.

[laravel] 다형성관계 시더작성하기

  • User모델은 Article 모델과 일대다 관계를 가진다.
  • Comment 모델은 Article 모델과 일대다 다형성 관계를 가진다.
  • Article은 제한이 없는 계층적인 댓글을 가진다.

0.테이블 스키마생성

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
});

Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('title');
            $table->text('content');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });

Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('parent_id')->nullable();
            $table->text('content');
            $table->string('commentable_type');
            $table->unsignedBigInteger('commentable_id');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('parent_id')->references('id')->on('comments');
        });

1. 모델간 관계 정의

User 모델 :

    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’를 전달해서 외래키를 지정했다.

2.시더에 사용할 팩토리 작성

UserFactory :

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

ArticleFactory :

$factory->define(Article::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence,
        'content' => $faker->paragraph,
    ];
});

Article은 User와 관계가 정의 되어있기 때문에 user_id를 따로 지정하지 않아도 자동으로 시더를 통해 입력된다.

CommentFactory :

$factory->define(Comment::class, function (Faker $faker) {
    $userIds = App\User::all()->pluck('id')->toArray();
    return [
        'content' => $faker->paragraph(),
        'user_id' => $faker->randomElement($userIds),
    ];
});

마찬가지로 ‘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’값을 전달해 줌으로써 재귀적인 댓글 구조를 가질수 있게 한다.

[laravel] 데이터베이스 시딩

시딩이란

라라벨은 시더클래스를 통해서 데이터베이스에 더미 데이터를 넣을수 있는 기능을 제공한다.

1. 시더클래스

아티즌 명령어를 통해 시더를 생성한다. 생성된 클래스는 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');
    }
}

팩토리 파일을 생성해준다. 옵션을 붙여 모델을 연결해주면 편리하다.

php artisan make:factory ArticleFactory --model=Article

생성된 파일을 열어 내용을 수정하자.

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개의 레코드가 생성된 것을 확인할 수 있다.


References

[laravel]인증기능 커스텀

라라벨은 기본적으로 인증기능을 지원하지만 상황에 따라 커스텀을 해야하는 경우가 있다.

목표

  • 라라벨인증 기능은 사용자가 이용하고, 커스텀한 인증은 관리자가 이용할 수 있게 한다.

목차

  1. 프로젝트 생성
  2. 라라벨인증 기능 설치
  3. 모델 및 마이그레이션 생성
  4. 모델 migrate
  5. 가드, 프로바이더, 패스워드 지정
  6. 미들웨어 생성 및 등록
  7. 라우트 생성
  8. 컨트롤러, 뷰 생성

1. 프로젝트 생성

laravel new blog
cd ./blog
php artisan key:generate
composer install

2. 라라벨 인증기능 설치

라라벨인증을 설치하는 이유

  1. 사용자는 기본인증을 사용하기로 함
  2. 커스텀인증은 처음부터 구현하는것 보다 기본인증을 복사해서 사용하는것이 시간을 절약할 수 있다.
composer require laravel/ui --dev
npm install
npm run dev

3. 모델 및 마이그레이션 생성

기본인증에 사용되는 User모델이 있지만 관리자는 새로운 테이블로 관리하려고하기 때문에 새로운 모델과 마이그레이션을 생성해야 한다.

하지만 메일인증에 사용하는 password_resets 테이블은 분리하지 않고 공통으로 사용하기로 한다.

php artisan make:model Admin -m

‘-m’ 옵션을 사용하면 마이그레이션도 같이 생성된다.

Admin 모델 정의

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class Admin extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

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

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

마이그레이션 파일을 열어 테이블 스키마를 생성한다.

    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admins');
    }

4. 모델 migrate

migrate 명령어를 통해 테이블을 생성해주자

php artisan migrate

5. 가드, 프로바이더, 패스워드 지정

config/auth.php 파일을 열어 생성한 Admin 모델에 대해 가드, 프로바이더, 패스워드를 추가해준다.

    'guards' => [

        ...

        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
 
        ...

        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
    ],

    'passwords' => [

        ...

        'admin' => [
            'provider' => 'admins',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
  • guards : 매요청마다 어떻게 인증할 것인가
  • providers : 저장소에서 사용자를 어떻게 찾을 것인가
  • passwords : 사용자 테이블에 대한 암호를 재설정하는데 사용하는 브로커

6. 미들웨어 생성과 등록

라우트를 보호할 목적으로 미들웨어를 생성한다. 기본인증에 사용되는 미들웨어 클래스 Authenticate , RedirectIfAuthenticated 를 동일하게 구현할 예정이다.

미들웨어 생성

php artisan make:middleware AdminAuthenticate
php artisan make:middleware AdminRedirectIfAuthenticated
  • AdminAuthenticate : 인증된 사용자가 아니면 로그인 페이지로 리다이렉트
  • AdminRedirectIfAuthenticated : 인증되지 않은 요청만 허용, 로그인된 사용자라면 홈으로 리다이렉트

AdminAuthenticate 미들웨어는 상속받은Illuminate\Auth\Middleware\Authenticate 클래스의 redirectTo메서드를 오버라이드 해서 리다이렉트 경로를 지정해준다.

#AdminAuthenticate.php

    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return route('admin.login');
        }
    }

AdminRedirectIfAuthenticated 는 클래스를 상속받기 않기 때문에 handle매서드를 지정해주면 된다. Auth파사드를 사용하기 위해 Illuminate\Support\Facades\Auth 네임스페이스 사용한다.

#AdminRedirectIfAuthenticated.php

use Illuminate\Support\Facades\Auth;

    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/admin/home');
        }

        return $next($request);
    }

정의한 미들웨어를 app/http/kernel.php 에 등록한다.

    protected $routeMiddleware = [
        
        ...

        'admin.auth' => \App\Http\Middleware\AdminAuthenticate::class,
        'admin.guest' =>\App\Http\Middleware\AdminRedirectIfAuthenticated::class,
    ];

7. 라우트 생성

라라벨 인증에 사용되는 라우트는 아래와 같다. 아래의 기본인증 라우트는 그대로 두고 새로 만든 admin전용 라우트를 생성한다.

  • 기본인증 라우트
MethodURINameActionMiddleware
GET|HEADloginloginApp\Http\Controllers\Auth\LoginController@showLoginFormweb,guest
POSTlogin App\Http\Controllers\Auth\LoginController@loginweb,guest
POSTlogoutlogoutApp\Http\Controllers\Auth\LoginController@logoutweb
GET|HEADregisterregisterApp\Http\Controllers\Auth\RegisterController@showRegistrationFormweb,guest
POSTregister App\Http\Controllers\Auth\RegisterController@registerweb,guest
GET|HEADpassword/confirmpassword.confirmApp\Http\Controllers\Auth\ConfirmPasswordController@showConfirmFormweb,auth
POSTpassword/conform App\Http\Controllers\Auth\ConfirmPasswordController@confirmweb,auth
GET|HEADpassword/resetpassword.requestApp\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestFormweb
POSTpassword/emailpassword.emailApp\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmailweb
GET|HEADpassword/reset/{token}password.resetApp\Http\Controllers\Auth\ResetPasswordController@showResetFormweb
POSTpassword/resetpassword.updateApp\Http\Controllers\Auth\ResetPasswordController@resetweb
GET|HEADemail/verifyverification.noticeApp\Http\Controllers\Auth\VerificationController@showweb,auth
POSTemail/resendverification.resendApp\Http\Controllers\Auth\VerificationController@resendweb,auth,throttle:6,1
GET|HEADemail/verify/{id}/{hash}verification.verifyApp\Http\Controllers\Auth\VerificationController@verifyweb,auth,signed,throttle:6,1
GET|HEADhomehomeApp\Http\Controllers\HomeController@indexweb,auth

위 라우트는 web.php에 정의되어 있지 않고 Auth::routes(); 로 등록되어 있다. 기본인증 라우트를 확인 하려면 /vendor/laravel/ui/src/AuthRouteMethods.php를 확인하면 된다.

  • admin 라우트
MethodURINameActionMiddleware
GET|HEADadmin/loginadmin.loginApp\Http\Controllers\Admin\LoginController@showLoginFormweb,admin.guest
POSTadmin/login App\Http\Controllers\Admin\ LoginController@loginweb,admin.guest
POSTadmin/logoutadmin.logoutApp\Http\Controllers\Admin\ LoginController@logoutweb
GET|HEADadmin/registeradmin.registerApp\Http\Controllers\Admin\ RegisterController@showRegistrationFormweb,admin.guest
POSTadmin/register App\Http\Controllers\Admin\ RegisterController@registerweb,admin.guest
GET|HEADadmin/password/confirmadmin.password.confirmApp\Http\Controllers\Admin\ ConfirmPasswordController@showConfirmFormweb,admin.auth
POSTadmin/password/conform App\Http\Controllers\Admin\ ConfirmPasswordController@confirmweb,admin.auth
GET|HEADadmin/password/resetadmin.password.requestApp\Http\Controllers\Admin\ ForgotPasswordController@showLinkRequestFormweb
POSTadmin/password/emailadmin.password.emailApp\Http\Controllers\Admin\ ForgotPasswordController@sendResetLinkEmailweb
GET|HEADadmin/password/reset/{token}admin.password.resetApp\Http\Controllers\Admin\ ResetPasswordController@showResetFormweb
POSTadmin/password/resetadmin.password.updateApp\Http\Controllers\Admin\ResetPasswordController@resetweb
GET|HEADadmin/email/verifyadmin.verification.noticeApp\Http\Controllers\Admin\VerificationController@showweb,admin.auth
POSTadmin/email/resendadmin.verification.resendApp\Http\Controllers\Admin\VerificationController@resendweb,admin.auth,throttle:6,1
GET|HEADadmin/email/verify/{id}/{hash}admin.verification.verifyApp\Http\Controllers\Admin\VerificationController@verifyweb,admin.auth,signed,throttle:6,1
GET|HEADadmin/homeadmin.homeApp\Http\Controllers\Admin\HomeController@indexweb,admin.auth
#routes/web.php

Route::namespace('Admin')->group(function(){
    Route::prefix('admin')->group(function(){
        //로그인
        Route::get('login', 'LoginController@showLoginForm')->name('admin.login');
        Route::post('login', 'LoginController@login');
        Route::post('logout', 'LoginController@logout')->name('admin.logout');
        //회원가입
        Route::get('register', 'RegisterController@showRegistrationForm')->name('admin.register');
        Route::post('register', 'RegisterController@register');
Route::get('home', 'HomeController@index')->name('admin.home');
        //패스워드확인
        Route::get('password/confirm', 'ConfirmPasswordController@showConfirmForm')->name('admin.password.confirm');
        Route::post('password/confirm', 'ConfirmPasswordController@confirm');
        //패스워드리셋
        Route::get('password/reset', 'ForgotPasswordController@showLinkRequestForm')->name('admin.password.request');
        Route::post('password/email', 'ForgotPasswordController@sendResetLinkEmail')->name('admin.password.email');
        Route::get('password/reset/{token}', 'ResetPasswordController@showResetForm')->name('admin.password.reset');
        Route::post('password/reset', 'ResetPasswordController@reset')->name('admin.password.update');
        //이메일인증 확인
        Route::get('email/verify', 'VerificationController@show')->name('admin.verification.notice');
        Route::post('email/resend', 'VerificationController@resend')->name('admin.verification.resend');
        Route::get('email/verify/{id}/{hash}', 'VerificationController@verify')->name('admin.verification.verify');
    });
});

8. 컨트롤러 및 뷰생성

(1) 컨트롤러 뼈대 만들기

app/Http/Controller 아래에 Admin 폴더를 생성
app/Http/Controller/Auth 하위 파일을 Admin 폴더로 복사한다.
app/Http/Controller/HomeController를 Admin 폴더로 복사한다.

이제 모델 Admin에 대한 컨트롤러는 아래와 같은 디렉토리 구조를 같는다.

  • app/Http/Controller/Admin/LoginController.php
  • app/Http/Controller/Admin/RegisterController.php
  • app/Http/Controller/Admin/ConfirmPasswordController.php
  • app/Http/Controller/Admin/ForgotPasswordController.php
  • app/Http/Controller/Admin/ResetPasswordController.php
  • app/Http/Controller/Admin/VerificationController.php
  • app/Http/Controller/Admin/HomeController.php

(2) 뷰 뼈대 만들기

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');
    }
admin/auth/password/confirm.blade.php
<form method="POST" action="{{ route('admin.password.confirm') }}">

특정라우트를 보호하고 싶을때는 아래와 같이 미들웨어를 지정한다. admin.auth에는 admin 가드를 전달, password.cofirm에는 리다이렉트 라우트를 전달한다.

        Route::get('test', function(){
            return '이페이지가 보인다면 비밀번호 확인을 하신겁니다.';
        })->middleware(['admin.auth:admin', 'password.confirm:admin.password.confirm']);
  • 비밀번호 리셋

라라벨은 기본적으로 이메일로 인증주소를 받아 비밀번호를 초기화 시킨다. 이메일 설정이 되어있지 않으면 메일형식을 로그로 변경해주자.

# .env
MAIL_MAILER=log

비밀번호를 초기화하는 과정은 아래와 같다.

  1. admin/password/reset 페이지 접근
  2. 사용자 정보가 있으면 비밀번호 초기화페이지로 이동 없으면 로그인
  3. 이메일을 제출하면 해당 주소로 인증주소를 보낸다.
  4. 인증주소로 접속하여 비밀번호를 변경

이제 컨트롤러와 뷰를 수정하자

#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전용 리셋주소로 정보를 받아야 한다.

패스워드 재설정 이메일을 커스텀하려면 몇가지 작업이 필요하다.

  1. artisan 명령어를 이용해 notification 생성
  2. Admin모델에서 sendPasswordResetNotification 메서드 오버라이드

패스워드 재설정 참조링크

php artisan make:notification AdminPasswordResetMailToken
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] file could not be downloaded (http/1.1 404 not fund) 해결방법

라라벨 설치하는 과정에서 아래와 같은 에러 발생

 [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)

아래의 명령어를 순서대로 입력후 재설치

composer clear-cache
composer -vvv

참조:
https://github.com/composer/composer/issues/7021

[laravel] 관례적인 네이밍 규칙

  • 모델은 단수형
    예)Post
  • 테이블은 복수형
    예)posts
  • 테이블간 관계를 표현할 때
    -테이블 이름_열이름_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을 사용한다.
idpost_idtag_id
111
212
  • 마이그레이션 생성시
    – create_, make_, add_, drop_, change_ 등으로 시작하고, _table로 끝난다.
    예) create_posts_table
  • 컨트롤러
    – 파스칼 표기법을 사용하여 복수형에 Controller접미사를 붙인다.
    예)PostsController

[php]함수에서 매개변수, 인자의 차이

매개변수는 함수를 정의 할때 정의 되고, 인자는 함수를 호출 할때 실제로 함수에 전달되는 값

function foo($color){
    echo "color: $color";
}

foo('red');

위 함수에서 정의 되는 $color는 매개변수, foo함수를 호출 할때 전달되는 ‘red’는 인자가 된다.

[ckeditor4] filetools-respone-error

사용한 버전 ckeditor4.13.1

이미지를 업로드하면 파라미터값(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>
#upload.php
<?php
if(isset($_FILES['upload']['name'])){
    $file = $_FILES['upload']['tmp_name'];
    $file_name = $_FILES['upload']['name'];
    $file_name_array = explode('.', $file_name);
    $extension = end($file_name_array);
    $new_image_name = rand().'.'.$extension;
    $allowed_extension = array('jpg', 'png', 'gif');
    if(in_array($extension, $allowed_extension)){
        $result = move_uploaded_file($file, 'upload/'.$new_image_name);
        $funcNum = $_GET['CKEditorFuncNum'];
        $url = 'upload/'.$new_image_name;
        $message = 'hello';
        echo "<script type='text/javascript'>window.parent.CKEDITOR.tools.callFunction($funcNum,'$url','$message');</script>";
    }
}
?>

https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-filebrowserUploadMethod