라라벨에서 테이블 변경 마이그레이션 추가 후 테스트가 깨졌다

개발 단계에서는 테이블 생성하는 마이그레이션을 고쳐가며 작업해도 괜찮습니다. 하지만 이미 애플리케이션을 배포한 뒤라면 그럴 수 없죠. 기존 테이블을 수정하는 마이그레이션을 작성해야 합니다.

오늘은 기존 테이블을 변하는 마이그레이션을 작성하다가 겪은 일을 공유하고자 합니다.

아래와 같이 아주 간단한 마이그레이션을 작성했습니다.

public function up()
{
    Schema::table('posts', function(Blueprint $table) {
       $table->renameColumn('movie_id', 'postable_id');
       $table->string('postable_type')->default('');
    });
}

posts 테이블에서 movie_id 컬럼명을 postable_id로 바꾸고, postable_type 컬럼을 하나 추가하는 것입니다.

개발 환경에서는 마이그레이션이 문제 없이 실행되었는데, 테스트를 돌려보니 에러가 납니다. 에러 메시지를 보니 postable_type 컬럼이 없다고 합니다.

table posts has no column named postable_type

분명히 개발 환경에서 마이그레이션할 때 문제가 없었는데 어떻게 된 일일까요?

문제는 SQLite 때문이었습니다. 저는 테스트 속도를 높이기 위해 테스트시에는 SQLite를 쓰고 있습니다. 근데, SQLite는 테이블을 수정할 때 한 번에 여러 컬럼을 추가할 수가 없다고 하네요. 그래서 테스트 환경에서는 두번째 작업인 postable_type 컬럼 추가가 진행이 안된 것입니다.

해결 방법은 간단합니다. 수정하는 마이그레이션인 경우 한 번에 하나씩만 바꾸도록 코드를 바꿔주면 됩니다.

 public function up()
{
    Schema::table('posts', function(Blueprint $table) {
       $table->renameColumn('movie_id', 'postable_id');
    });

    Schema::table('posts', function(Blueprint $table) {
        $table->string('postable_type')->default('');
    });
}

물론 테스트 할 때 SQLite 안쓰시는 분은 신경쓰지 않으셔도 됩니다.

라라벨 5.8.16 새기능(2)

라라벨 5.8.16에서는 이전에 소개한 마이그레이션 이벤트 이외에 두가지 기능이 더 추가 되었습니다.

하나는 PostgreSQL을 사용하는 사람을 위한 기능으로, migrate:fresh 할 때 type을 지울 수 있는 옵션이 추가된 것입니다. 개발자에 의하면 PostgreSQL 에서는 ENUM에 타입을 사용하는데 migrate:fresh를 하면 테이블은 다 지워지지만 이 타입이 남아서 문제가 생겼었다고 하네요. 데이터베이스 뷰를 지우는 옵션을 사용하는 것과 같은 방법으로 사용하면 된다고 합니다. (데이터베이스 뷰를 지우는 옵션도 있었군요…ㅎㅎ)

php artisan migrate:fresh --drop-types

다른 하나는 MailMessage 클래스에 Renderable 컨트랙트를 추가한 것입니다. 이를 통해 알림(Notification) 메일이 어떻게 보내질 것인지 브라우저로 확인해볼 수 있다고 하네요. 예를 들어, 컨트롤러에서 다음과 같이 하면 된다고 합니다.

return (new FooNotification())->toMail('example@example.com');

브라우저로 메일 내용을 미리보는 건 Mailable 클래스에 이미 있던 기능인데, 같은 기능을 알림 메일에 사용하는 MailMessage 클래스에도 추가했다고 합니다.

그렇지 않아도 이번에 막 알림을 메일로도 받을 수 있게 작업하려는 참이었는데 잘됐네요 ㅎㅎ

라라벨 5.8.16 새기능(1) – 마이그레이션 이벤트 추가

Illuminate\Database\Events 네임스페이스에 아래 이벤트가 추가되었습니다.

  • MigrationEnded
  • MigrationsEnded
  • MigrationStarted
  • MigrationsStarted

이 기능을 제안하고 추가한 알렉스 보워스가 밝힌 용도는 마이그레이션을 시작할 때 캐시를 지우거나, 마이그레이션 시작과 종료를 모니터링하는 것 등 입니다.

참고

라라벨 5.8.9 – 이벤트 발견 기능 추가

5.8.8 까지는 어떤 이벤트가 발생하면 어떤 리스너가 작동해야하는지 직접 적어줬어야 했습니다. 아래와 같은 식이죠.

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'App\Events\OrderShipped' => [
        'App\Listeners\SendShipmentNotification',
    ],
];

5.8.9 부터 이벤트 발견 기능이 추가되어 이벤트와 리스너의 관계를 직접 등록하는 수고를 덜게 됐습니다.

이벤트 발견 기능은 기본적으로 비활성화 되어 있습니다. 이벤트 발견 기능을 사용하려면 아래와 같이 EventServiceProvidershouldDiscoverEvents 메소드를 오버라이드해야 합니다.

/**
 * Determine if events and listeners should be automatically discovered.
 *
 * @return bool
 */
public function shouldDiscoverEvents()
{
    return true;
}

이벤트 발견 기능을 활성화하면 이벤트 캐시 파일이 있으면 이를 이용하고, 없으면 실시간으로 이벤트를 찾습니다. 실시간으로 리퀘스트를 찾으면 애플리케이션이 느려지기 때문에 되도록 캐시를 사용하는게 좋습니다. artisan event:cache 명령으로 캐시할 수 있습니다.

Laravel Collection 메소드 중 concat과 push의 차이

라라벨 5.8.8에서 쿼리 빌더에 forPageBeforeId 메소드가 추가되었습니다. 뭔지 알아보려고 PR을 보는데, 예제에서 컬렉션 메소드 중 concatpush를 쓰더군요.

$posts = new Collection;

$posts = $posts->concat(ChatPost::forPageBeforeId(15, $focused->id)->get()->reverse());

$posts = $posts->push($focused);

$posts = $posts->concat(ChatPost::forPageAfterId(15, $focused->id)->get());

둘 다 제가 잘 안쓰던 메소드들이라 메뉴얼을 찾아봤는데, 읭? 둘이 똑같아 보이는 겁니다.

push 메소드는 컬렉션의 마지막에 아이템을 추가합니다:

concat 메소드는 주어진 배열 또는 컬렉션의 마지막에 값을 추가합니다:

그래서 소스를 봤는데, 소스를 보니 차이점을 알겠더라구요.

    /**
     * Push all of the given items onto the collection. 
     *
     * @param  iterable  $source
     * @return static
     */
    public function concat($source)
    {
        $result = new static($this);

        foreach ($source as $item) {
            $result->push($item);   // 주어진 아이템들을 push를 사용해서 추가
        }

        return $result;
    }

push는 하나의 아이템을 컬렉션의 마지막에 추가하는 거고, concatpush 메소드를 이용해서 여러 아이템을 한 번에 추가하는 거였어요.

뭐 별로 중요한 얘기는 아니었습니다 하핫

익혀야할 것

오늘 업무를 종료하며 내일은 아래 두 가지를 익혀야겠다고 생각했습니다.

  1. Laravel HTTP 테스트에서 Mockery를 사용하는 방법
  2. Laravel HTTP 테스트 실행시 xdebug 로 디버깅하는 방법

오늘은 테스트를 작성하면서 삽질을 많이했는데, 첫번째 것은 오늘 삽질 결과 알아낸 해결책이고, 두번째 것은 오늘과 같은 삽질을 덜 고통스럽게 하는 해결책입니다.

Laravel elixir version 기능이 제대로 작동하지 않는 경우

몇 주만에 라라벨로 만든 애플리케이션을 수정하려고 했는데, gulp 명령어를 실행하니 에러가 났습니다.

SyntaxError in plugin 'run-sequence(version)'
Message:
Unexpected token s in JSON at position 41
Stack:
SyntaxError: Unexpected token s in JSON at position 41
at Object.parse (native)
at VersionTask.deleteManifestFiles (/home/vagrant/Code/bookcafe100.com/node_modules/laravel-elixir/dist/tasks/VersionTask.js:113:29)
at VersionTask.gulpTask (/home/vagrant/Code/bookcafe100.com/node_modules/laravel-elixir/dist/tasks/VersionTask.js:71:18)
at VersionTask.run (/home/vagrant/Code/bookcafe100.com/node_modules/laravel-elixir/dist/tasks/Task.js:138:31)
at Gulp.<anonymous> (/home/vagrant/Code/bookcafe100.com/node_modules/laravel-elixir/dist/tasks/GulpBuilder.js:65:67)
at module.exports (/home/vagrant/Code/bookcafe100.com/node_modules/orchestrator/lib/runTask.js:34:7)
at Gulp.Orchestrator._runTask (/home/vagrant/Code/bookcafe100.com/node_modules/orchestrator/index.js:273:3)
at Gulp.Orchestrator._runStep (/home/vagrant/Code/bookcafe100.com/node_modules/orchestrator/index.js:214:10)
at Gulp.Orchestrator.start (/home/vagrant/Code/bookcafe100.com/node_modules/orchestrator/index.js:134:8)
at runNextSet (/home/vagrant/Code/bookcafe100.com/node_modules/run-sequence/index.js:86:16)

한참 삽질하다가 Elixir version is messing up 라는 글에서 ‘public/build/rev-manifest.json 파일이 아래와 같이 되어 있기 때문이라는 걸 알았습니다.

{
"js/app.js": "js/app-e81f312e80.js"
}s"
}

아직까지 rev-manifest.json 파일이 왜 저리 되었는지 원인은 못찾아냈습니다만, 문제를 유발하는 s” } 를 지우니 빌드가 됐습니다. 아오 답답해서 혼났네요.

 

leaderboard-728x90

 

라라벨은 시맨틱 버저닝을 사용하지 않는다

최근에 라라벨 책을 저술하신 두 저자분 께서 라라벨이 마이너 업데이트 되었는데 예제 소스코드가 정상적으로 작동하지 않아서 고생하신 것을 본 적이 있습니다. 이와 관련하여 정광섭님이해할 수 없는 라라벨의 릴리스 관리 정책 이란 글을 올리기도 하셨고, 또 김주원님도 관련하여 비판을 하셔서 다른 사람들은 어떻게 생각하나 좀 찾아봤습니다. 여러 사람들이 라라벨 제작자에게 시맨틱 버저닝을 사용하는지 물어보기도 하고, 또 왜 안쓰냐고 따지기도 했던거 같습니다. 이에 대해 아래 보시는 것과 같이 라라벨 제작자인 테일러 오트웰이 직접 라라벨은 시맨틱 버저닝 대신 독자적인 버저닝 시스템을 가지고 있다고 답변하는 걸 발견했습니다.

컴포저(Composer)가 시맨틱 버저닝을 사용하기 때문에, 라라벨도 당연히 시맨틱 버저닝을 사용할거라 생각했는데 의외네요.

스크린샷 2016-06-05 오전 2.55.32

위의 대화 이전에 라라벨 5.0을 발표하기 전에 시맨틱 버저닝을 도입하는 것에 대해 의견을 물은 것 같더군요. 여튼, 시맨틱 버저닝을 사용하지 않고 독자적인 버저닝 시스템을 사용하기로 한 것 같습니다.

스크린샷 2016-06-05 오전 2.57.55

See guzzles discussion on this 라고 해서 찾아봤는데, 이게 맞나 모르겠습니다. 이 링크에서 확인되는 Guzzle 의 버저닝 규칙은 다음과 같습니다.

Essentially, the current versioning scheme works like this

현재(글이 쓰여진 시점인 2013년) 버저닝 제도는 이렇게 작동합니다

 

: For a given version number X.Y.Z,

버전 넘버 X.Y.Z 에서

 

X represents fundamental or paradigm changes to the project,

X 는 프로젝트에 근본적이거나 패러다임의 변화가 있음을 나타냅니다.

 

Y represents major changes or feature additions (possibly including backwards-incompatible changes), and

Y 는 주된 변화나 기능 추가(하위 호환성이 지켜지지 않는 변화를 포함할 수 있음)를 나타내고,

 

Z represents minor changes and fixes that are backwards-compatible.

Z 는 하위 호환성이 보장되는 미미한 변화나 버그 수정을 나타냅니다.

라라벨이 Guzzle 과 같은 버저닝 규칙을 따른다면, 라라벨에서의 Y 는 SemVer 의 X 인 셈입니다. Y 가 변경된 경우, 시맨틱 버저닝일거라고 생각하고 안심하고 업데이트 하다가는 애플리케이션이 갑자기 돌아가지 않는 황당한 경험을 하실 수 있으니 유의하세요.

[참고]

시맨틱 버저닝에 대해 궁금하신 분은 Semantic Versioning 소개 나 공식 문서(한글)를 읽어보세요.

leaderboard-728x90

XECON 2015 Learning Laravel 발표자료

최근에 좋은 튜토리얼들이 쏟아져나와서 학습 전략이라는 말이 다소 무색해지긴 했지만 그래도 궁금해하시는 분들이 계실 수 있을 것 같아 발표자료를 공유해봅니다.

 

leaderboard-728x90

Laravel 마이그레이션 작성시 index 존재 여부 확인하는 방법

Laravel에 테이블이나 컬럼이 존재하는지 확인하는 메소드는 있는데 index 존재 여부를 확인하는 메소드는 지원하지 않아서 다소 아쉬운 면이 있었습니다. 찾아보니 doctrine schema manager 를 사용하면 확인이 가능하더군요. Laravel로 마이그레이션 작성해보신 분들은 아래 예제 코드 보시면 바로 이해가 되실거에요. 아마 doctrine/dbal 패키지를 설치가 필요할 거에요.(확인해보진 않았습니다 ^^ 어차피 renameColumn 하려면 필요하니까 걍 설치 고고)

Schema::table('articles', function($table)
{
    $conn = Schema::getConnection();
    $dbSchemaManager = $conn->getDoctrineSchemaManager();
    $doctrineTable = $dbSchemaManager->listTableDetails('articles');

    if($doctrineTable->hasIndex('title')){
        $table->dropIndex('title');
    }
});

 

leaderboard-728x90