[1일 1식 라라벨 샘플] route:list를 JSON으로 출력하는 기능이 추가되었다

라라벨 5.8.25가 나왔다. (같은 날 긴급 패치가 나와서 현재 최신 버전은 5.8.26이다.) 5.8.25에서는 route:list를 JSON으로 출력하는 기능이 추가되었다.

route:list

route:list는 터미널에서 전체 라우트를 테이블 형태로 출력하는 아티즌 명령어이다. 아래와 같이 실행할 수 있다.

php artisan route:list

아래는 모던 PHP 유저 그룹 홈페이지 의 라우트를 출력한 예시이다.

route:list를 JSON으로 출력하기

라우트 목록을 JSON으로 출력하고 싶으면 --json 옵션을 붙이면 된다.

php artisan route:list --json

그러면 아래와 같이 JSON 형식으로 출력된다.

왜 만들었을까?

이 기능을 추가한 제이슨 맥크레리“프로그램으로 쉽게 파싱될 수 있어서 다른 도구나 서비스에 의해 쓰이기 좋다”고 이유를 밝혔다. 그런데 다른 도구나 서비스가 아티즌 커맨드를 쓰는 일이 있나 싶다.

기존에도 원한다면 라우트 파사드를 통해 이용해서 전체 라우트를 조회할 수 있었다.

$routes = \Illuminate\Support\Facades\Route::getRoutes()->get();

// get() 메소드에 조회하고자 하는 HTTP 메소드를 지정할 수도 있다.
$getRoutes = \Illuminate\Support\Facades\Route::getRoutes()->get('GET');

굳이 추가로 만든 이유를 찾자면 몇가지 항목으로 미리 정리해서 뽑아주기 때문에 살짝 더 편리할 수 있다 정도 아닐까? 아티즌 커맨드로 라우트 목록을 받으려면 아래와 같이 하면 된다.

Artisan::call('route:list --json');
$routes = Artisan::output();

어디에 쓰지?

전 직장에 입사했을 때, 이미 라라벨로 만든 애플리케이션이 잘 돌아가고 있었다. 다만, 테스트가 하나도 없었다. 사장님이 우선은 접속 안되는 페이지가 없는지 확인하는 테스트만 있어도 좀 안심이 될 것 같다고 이야기했고 나도 동의했다. 그때는 라우트 목록을 JSON으로 뽑는 기능도 없었고, 라우트 파사드로 조회할 수 있는지도 몰라서 아래와 같이 단순 무식하게 짰다.

$this->get("/")->assertSuccessful();

$this->get("/user/login")->assertSuccessful();
$this->get("/user/add")->assertSuccessful();
$this->get("/user/forget")->assertSuccessful();
... //이렇게 수십 줄이 이어진다.

// 로그인이 필요한 페이지는 이런 식으로
$this->actingAs($user)->get('/user/changePassword')->assertSuccessful();

지금 다시 짠다면 요번에 추가된 기능이나 라우트 파사드를 이용해서 좀 더 프로그램 답게(programmatically) 작성할 수 있지 않을까 싶다. GET 메소드만 쌱 조회해서 미들웨어에 auth 있나 없나 보고 그에 따라 샤샥.

이 글은 하루에 한 편씩 라라벨 관련 글을 메일로 보내드리는 [1일 1식 라라벨] 의 샘플 원고입니다. 조금 더 길어질 수도 짧아질 수도 있습니다만, 어느 정도 공들여 쓴 블로그 포스트 정도라고 생각해주시면 될 것 같습니다. 7월호 유료구독자를 모집하고 있습니다.

[1일 1식 라라벨] 구독자 모집

안녕하세요. 이현석입니다. 저는 PHP를 주로 사용하는 개발자이고 개발 관련 책과 글도 씁니다. 출간한 책으로는 “바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서“가 있습니다. 대외활동으로는 모던 PHP 유저 그룹에서 꾸준히 활동 중이고, 코딩 야학에도 조력자로 참여하고 있습니다. 라라벨 관련해서는 XE 기술 세미나와 XECON에서 발표한 바 있고, 패스트캠퍼스에서 강의한 바 있습니다.

일간 이슬아와 이다님의 매일마감을 보고 용기내서 유료 뉴스레터를 시작합니다. 라라벨을 4 시절부터 써오고 있는데, 라라벨의 인기와 영향력은 날이 갈수록 늘어서 해외에서는 매일 새로운 자료들이 쏟아져 나오고 있습니다. 일 안하고 하루종일 읽기만 해도 과연 다 소화할 수 있을까 의문이 들 정도입니다. 한편, 한글로 된 자료는 좀처럼 찾아보기가 쉽지 않습니다. 그래서 매일 열심히 새로 나오는 라라벨 관련 정보들을 익히고, 하루에 한 편씩 알기 쉽게 정리해서 메일로 보내주는 일을 해보고자 합니다. 뉴스 큐레이션은 아닙니다. 샘플 원고는 여기서 확인하실 수 있습니다.

구독료는 한 달 1만원 입니다. 6월 말일까지 신청받고, 7월 1일부터 시작해서 7월 말일 까지 주중에 한 편씩 글을 보내드립니다. 주말엔 쉽니다.

보내드리는 글은 시간이 지나면 모든 사람들에게 공개할 예정입니다. 마음이 바뀌어 공개를 안할 수도 있습니다만, 일단은 3주 정도 후에 공개할까 합니다.

제 자신은 많은 개발자들이 무료로 공개한 자료로 학습했으면서, 글을 유료로 판매하는 것에 대해 다소 죄스런 마음이 있습니다. 하지만 앞서 말씀드렸듯 한글로 된 자료가 매우 희박한데, 그 이유는 지식을 공유할 만큼 여유로운 개발자의 수가 충분히 많지 않아서라고 생각합니다. 그래서 누군가는 여가시간이 아닌 일과시간을 이용해서 자료를 만들고 그것으로 돈을 버는 것도 의미가 있다고 믿습니다.

한 분 이라도 신청해주시면 진행합니다. 단 한 분만 구독하시더라도 최선을 다하겠습니다.

신청하시는 곳 https://forms.gle/rB4jFH6CYDETqpR97

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

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

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

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

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 새기능(1) – 마이그레이션 이벤트 추가

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

  • MigrationEnded
  • MigrationsEnded
  • MigrationStarted
  • MigrationsStarted

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

참고

라라벨 5.8.11 – Date 파사드에 macro 메소드 추가

하 벌써 5.8.13이 나왔던데 뭔 업데이트가 이리 빠른가요.. 여튼 한 박자 늦긴 했지만 5.8.11 에 변경된 사항을 정리해봅니다.

Date 파사드에 macro 메소드 추가

Date::macro('example', function () {
    return 'hello';
});

Date::example(); // hello

위의 예시에서 보듯이 macro 메소드는 런타임에 인스턴스에 메소드를 추가할 수 있게 해줍니다. macro 메소드는 컬렉션에도 있고 리스폰스에도 있다는데 아직 써보진 못했어요. 왜 이 기능을 추가하기로 했는지 궁금해서 PR을 찾아봤는데 별 내용이 없더군요.

다행히 Carbon에 설명이 있어서 용도를 가늠해볼 수 있었습니다. Carbon 문서에서는 설정이나 유저 선호에 따라 날짜를 출력할 때 쓰기 좋다고 이야기하고 있습니다. 아래는 Carbon 문서에 있는 예시입니다. 파리 시간과 프랑스어로 바꾸고 calendar 메소드를 실행한 결과를 출력하도록 formatForUser 메소드를 추가합니다.

<?php
// Let assume you get user settings from the browser or preferences stored in a database
$userTimezone = 'Europe/Paris';
$userLanguage = 'fr_FR';

Carbon::macro('formatForUser', function () use ($userTimezone, $userLanguage) {
    $date = $this->copy()->tz($userTimezone)->locale($userLanguage);

    return $date->calendar(); // or ->isoFormat($customFormat), ->diffForHumans(), etc.
});

// Then let assume you store all your dates/times in UTC (because you definitely should)
$dateString = '2010-01-23 10:00:00'; // Get this from your database or any input

// Then now you can easily display any date in a page/e-mail using those user settings and the chosen format
echo Carbon::parse($dateString, 'UTC')->formatForUser();   // 23/01/2010

덕분에 이른바 Macroable 이라는 특성에 대해 생각해보는 기회가 됐네요. 현재까지 이해한 바로는 기존 클래스를 확장하기엔 불편하고, 헬퍼보다는 더 체계적이고 싶을 때 쓰면 좋은 것 같습니다. 가령 Collection을 확장해서 MyCollection을 만들면 엘로퀀트 ORM도 Collection 대신 MyCollection을 반환하게 손을 대는 등 프레임워크의 여러 부분에 손을 대야 할 것 같아요. 메소드 하나 추가하자고 일이 너무 커져버리니까 이런 경우에 macro라는 컨셉을 쓰나 봅니다. 이 부분은 잘 몰라서 뇌피셜을 쓴 것이니 바로잡아주실 필요가 있으면 댓글 부탁드려요.

5.8.11에 추가된 게 두 가지 더 있지만 그건 다음에 기회되면 다룰께요.

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