라라벨 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

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

소감

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

Mac에서 Docker로 개발환경 구성시, Internet Explorer로 테스트하는 방법

모든 웹브라우저가 다 똑같이 동작하면 참 감사할텐데, 실상은 그렇지 않죠. 특히 IE! 뭐 어쩌겠습니까. 고객님들이 쓰는 주요 브라우저는 다 테스트 해봐야지요.

Mac을 사용하는 개발자들의 문제는 Mac에 IE가 설치되지 않는다는 점입니다. 다행히 모던 PHP 유저 그룹의 a2님으로부터 VirtualBox의 가상 머신으로 도커 머신에 접근하는 방법을 배울 수 있었습니다. 저만 배우고 입 싹 닦을 순 없으니 방법을 정리해서 공유합니다.

Virtualbox 네트워크 만들기

virtualbox를 실행하고 ‘전역도구’에서 호스트 네트워크 관리자를 선택합니다. 만들기를 클릭해서 호스트 네트워크를 하나 만듭니다. 성공하면 위와 같이 네트워크가 하나 만들어집니다.

가상 머신에 네트워크 연결

사용할 윈도우 가상 머신 설정으로 들어가서 네트워크에 ‘호스트 전용 어댑터’로 방금 만든 네트워크를 추가해줍니다.

윈도우 host 설정

virtualbox로 윈도우를 띄웁니다. 윈도우에서 테스트할 도메인을 앞서 만든 네트워크에 연결합니다. 예를 들어, 윈도우10인 경우, c:\windows\system32\drivers\etc\hosts 파일을 열고 아래의 내용을 추가합니다. 아래의 IP주소는 앞서 만든 vboxnet0의 IP입니다. 여러분은 여러분 것을 사용하시면 됩니다.

192.168.56.1 dev.example.com

도커 포트 설정

도커로 개발환경을 띄울 때 포트에 192.168.56.1:80:80을 추가해서 띄워줍니다. 저는 docker-compose를 사용하기 때문에 아래와 같이 docker-compose.yml 파일에서 ports에 한 줄 추가해주면 되었습니다.

이렇게하면 virtualbox로 띄운 윈도우에서 dev.example.com 접속시 호스트 컴퓨터의 도커 머신으로 접속하게 됩니다.

모쪼록 크로스 브라우징 작업에 조금이나마 스트레스가 줄길 바랍니다. 🙂

Chromium으로 스크린 캡쳐를 했는데 한글이 네모로 나오는 경우

자동으로 스크린샷을 저장하기 위해 Browsershot을 사용했다. 그런데, 한글이 제대로 표시되지 않아서 한참 삽질했다. 다행히 포기하기 직전에 기적적으로 해결 방법을 발견했다. 원래는 일본어가 동일한 증상으로 표현이 안되는 문제에 대한 글인데, 답변자가 친절하게도 한글 해결책도 함께 제시해줬다. 결론은 ttf-unfonts-core를 설치해주면된다.

sudo apt-get install ttf-unfonts-core

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가 예외를 발생시키면, 트랜젝션이 롤백되지 않은채로 테스트가 멈추기 때문에 락이 걸린 채로 다음 테스트가 실행되나 봅니다.

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

오늘의 삽질 로그 끝

PSR-2 강제하기

오랜만에 모던 PHP 유저 그룹에서 발표를 했습니다.

희대의 폭망 발표가 됐지만, 그래도 준비한 게 있으니 정리해서 정기모임 발표 자료 저장소에 올려두었습니다.

제목은 PSR-2 강제하기입니다. 표준 스타일을 지키자고 합의를 했다고 해도, 매번 상대방이 코딩 표준을 지켰는지 확인하는 건 번거로운 일이죠. 책을 쓰면서 조사하다보니 코딩 표준을 지키지 않으면 커밋을 못하도록 하는 아주 간단한 방법이 있어서 소개해봤습니다.

PSR-2 강제하기

 

[참고자료]

Enforce code standards with composer, git hooks, and phpcs