라라벨 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 메소드를 이용해서 여러 아이템을 한 번에 추가하는 거였어요.

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

라라벨 제작자가 추천한 라라벨 코드 깔끔하게 짜는 방법 초간단 요약

얼마전에 테일러 오트웰이 더 깔끔한 코드를 짜고 싶으면 참고하라며 링크 두개를 던져줬습니다.

두 자료를 초간단 요약해봤습니다.

Methods Are Affordances, Not Abilities

첫번째 자료는 Adam Wathan의 블로그 글인데 “메소드는 그 클래스’가’ 무얼 할 수 있는지가 아니라 그 클래스’를 가지고’ 무얼 할 수 있는지로 봐야한다”라는 내용입니다.

Announcement::create(request(['subject', 'message']))->broadcast();

위와 같은 코드를 예로 설명했는데, 메소드를 클래스를 가지고 무얼 할 수 있는지라고 생각하면 매우 자연스럽습니다. ‘Announcement를 broadcast한다’ 자연스럽죠. 반면, 메소드를 클래스가 할 수 있는 것이라고 생각하면 Announcement가 스스로를 broadcast 하는게 어색해서 AnnouncementBroadcaster 같은 클래스를 만들게 됩니다. 이러면 괜히 코드가 복잡해진다고 합니다. 자세한 내용은 원문에서 🙂

Cruddy by Design

두번째 자료는 라라콘 2017의 발표 영상인데, 이 역시 Adam Wathan의 발표입니다.

레일즈를 만든 David Heinemeier Hansson이 “사람들이 레일즈를 쓸 때 컨트롤러를 너무 적게 쓴다”고 지적한 것을 계기로, 어떻게하면 컨트롤러가 많게 코드를 작성할 수 있는지 고민했나봅니다.

이 분이 알려준 비법은 Never Write Custom Action 입니다. CRUD에 사용하는 기본 메소드 7가지(index, create, store, show, edit, update, destroy)만 사용하라는 것입니다. 이외에 메소드를 추가하는 상황이 오면, 메소드를 추가하는 대신 새로운 컨트롤러를 추가합니다. 영상에서는 구체적인 기법 4가지를 소개합니다.

  • Tip1. Nested resource? New Controller
  • Tip2. Edited independently? New Controller
  • Tip3. Touches pivot records? New Controller (and probably a new model)
  • Tip4. Transitions state? New Controller

역시나 자세한 건 원본 영상에서 🙂

소감

영어라 읽고 보기 힘들었지만 ㅠ 그래도 보고나니 괜히 전보다 좀 더 깔끔한 코드를 짤 수 있을 것 같은 느낌적인 느낌이 듭니다. 하핫

MySQL 트랜젝션은 auto_increment 값을 되돌리지 않음

라라벨 애플리케이션에서 아래와 같은 테스트를 작성했습니다. 이해를 돕기 위해 구체적인 내용은 생략했습니다.

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SampleTest extends TestCase
{
    use RefreshDatabase;

    testFirst()
    {
        //테이블 A에 데이터 10개 추가
        //테스트 수행
    }

    testSecond()
    {
        //테이블 A에 데이터 11개 추가
        //테이블 A에서 데이터 10개를 ID 역순으로 조회 후, 10번째의 데이터의 ID가 값을 확인하는 테스트
    }
}

RefreshDatabase 트레이트를 사용했기 때문에 testSecond 테스트에서 ID는 2가 될 것으로 예상했습니다. 하지만 12가 나와서 테스트를 통과하지 못하더군요.

처음에는 RefreshDatabase의 트랜젝션이 동작하지 않는다고 의심했는데, 알고보니 트랜젝션이 auto_increment 값은 되돌리지 않았기 때문이었습니다.

이전 테스트가 다음 테스트에 영향을 미치지 않아야하지 않나 생각하는데, RefreshDatabase를 쓸 때는 auto_increment 값은 롤백되지 않는다는 점에 주의해서 테스트 코드를 작성해야겠습니다.

Mockery::close() 가 예외를 발생시키면 DatabaseTransactions 트레이트가 동작하지 않음

메소드 하나만 테스트 돌렸을 땐 통과되던게, 파일을 통으로 돌리니까 에러가 나더군요.

에러가 나는 원인을 보니, 데이터베이스에서 락이 걸렸기 때문이었습니다.

DatabaseTransactions 트레이트를 쓰고 있어서, 이전 테스트가 다음 테스트에 영향을 줄 이유가 전혀 없어보이는데, 대체 락이 왜 걸릴까? 찾다보니 원인은 Mockery 때문이었습니다. 이 링크 덕분에 알게 됐어요.  이 글 없었으면 며칠 날릴뻔 했네요. 소중한 정보 공유해준 얼굴 모를 개발자에게 오늘도 감사를!

Mockery를 쓰려고 했다가 필요 없어져서 테스트 코드에서는 Mockery 쓰는 부분을 다 제거했는데, 종료하는 코드를 남겨뒀더라구요.

public function tearDown() {
    Mockery::close();
}

종료할 Mockery가 없는데 종료를 해서 예외가 발생했었나봅니다. Mockery가 예외를 발생시키면, 트랜젝션이 롤백되지 않은채로 테스트가 멈추기 때문에 락이 걸린 채로 다음 테스트가 실행되나 봅니다.

위 코드를 제거하고 돌리니 잘 되네요.

오늘의 삽질 로그 끝