다른 언어들에 비해 유독 PHP는 중급자용 서적을 찾아보기가 어렵습니다. PHP가 워낙 쉬운 언어, 간단한 웹사이트 만들 때 쓰는 것이라는 이미지가 있기 때문인지, 아니면 중급자용 서적을 출간했다가 크게 실패를 해서인지는 모르겠습니다.
하지만 다른 언어들이 발달하는 만큼 PHP도 발전했고, 단순한 개인 홈페이지 뿐만 아니라 엔터프라이즈급 애플리케이션을 만드는데도 쓰입니다.
실제로 PHP로 자체 서비스를 운영하는 곳들은 대규모 처리, 대형 애플리케이션을 다룰 줄 아는 개발자들을 필요로 합니다. 그래서 PHP로 프로그래밍을 입문하는 사람들도 단순히 PHP를 다루는 방법을 익히는데 그치지 않고 더 좋은 소프트웨어를 만드는 방법을 배워야한다고 생각합니다.
그래서 현직 팀장들이 신입이 갖췄으면 하는 역량을 위주로 정리한 “바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서”를 쓰기도 했습니다. “바쁜..”은 아키텍처, 디자인 패턴, 객체지향 프로그래밍 등은 다루지 않고 있어서 다소 아쉬운 마음이 있었어요. 그러던 와중에 유익하게 읽었던 클린 아키텍처 인 PHP를 번역 출판할 기회를 얻게되어 즐거운 마음으로 번역에 도전했습니다.
처음해보는 번역이라 생각보다 힘들었습니다 ㅠ 모쪼록 더 나은 애플리케이션을 작성하는데 관심있는 모든 PHP 개발자 여러분들께 조금이나마 도움이 되길 바랍니다.
아래 구매링크에서 전체 목차와 샘플 챕터를 보실 수 있습니다. 많은 관심과 정보 공유 부탁드려요 ^^
이 글은 2019년 7월 15일에 1일 1식 라라벨에 발행된 글입니다. 일부 시점이나 버전 정보가 블로그 발행시점과 다를 수 있습니다. 8월호 구독자를 모집하고 있습니다. 월 1만원으로 최신 라라벨 소식을 받아보세요.
충분한 여유를 가지고 프로젝트를 진행한다면 코드를 작성하기 전에 설계도 하고, ERD(Entity Relation Diagram)도 그리고 할 것이다. 하지만 세상은 언제나 나에게 호의적이지만은 않다. 사전에 계획 문서를 충분히 작성할 시간이 부족한 프로젝트를 맡게 될 수도 있고, 처음엔 그리 복잡하지도 않고 혼자 진행해서 문서를 만들지 않고 진행했을 수도 있다. 그런데 이후에 클라이언트가 문서를 요구하거나, 혹은 동료가 더 생겨서 코드를 빠르게 파악하는데 도움이 되는 문서가 필요해지는 경우가 있다. Laravel ER Diagram Generator는 이런 경우에 요긴하게 쓸 수 있는 패키지이다.
Laravel ER Diagram Generator는 모델 파일에 정의된 관계를 분석해서 ERD를 생성한다. 다음은 Laravel ER Diagram Generator로 생성한 Laravel.io의 ERD이다.(출처: 공식 저장소)
트위터에 코드를 먼저 짜고 분석을 나중에 하냐는 조롱조의 글도 있다. 하지만 앞서 언급한 것 처럼 내 손에 넘어오는 시점에 어떠한 문서도 없이 코드만 달랑 넘어오는 경우가 분명히 있고, 이럴 때는 코드를 기반으로 문서를 만들어주는 도구가 유용하게 쓰일 수 있다. 실무적인 용도 외에 학습용으로도 꽤 유용할 수 있다. 타인의 코드로 학습하기 때문에 코드에서 부터 분석을 시작할 수 밖에 없기 때문이다.
조금 아쉬운 점은 엘로쿼트 모델을 상속받은 엔티티만이 분석 대상에 포함된다는 점이다. 도메인 주도 개발을 지향하거나, 클린 아키텍처를 지향하는 경우 라라벨에 의존하지 않는 도메인 모델과 서비스를 핵심으로 삼을 수도 있는데, 이러면 이 패키지로는 분석이 불가능하다.
--5.8.26이라는 옵션은 존재하지 않는다며 설치 실패! 두 방법 모두 내가 원하는 버전이 설치 안되는 수준이 아니라 아예 설치 자체가 안된다.
laravel/laravel과 laravel/framework
왜일까? 위의 컴포저 명령문을 다시 한 번 자세히 살펴보면 laravel/laravel 패키지를 설치를 시도한다는걸 알 수 있다. laravel/laravel의 깃헙 저장소에 가서 릴리즈 내역을 보면 최신 버전이 5.8.17이다. 5.8.27은 어디로 간거지?
5.8.17로 설치해보면? 설치된다.
버전을 확인해볼까?
5.8.27!??
이런 현상이 벌어지는 이유는 라라벨의 구조 때문에다. 라라벨은 laravel/laravel과 laravel/framework 패키지를 가지고 있다. laravel/framework가 라라벨 코어 프레임워크이고 laravel/laravel은 laravel/framework를 이용한 뼈대(skeleton) 애플리케이션이다. 즉 laravel/laravel은 일종의 퀵스타트 예제인 셈이다. laravel/laravel과 laravel/framework의 관계는 appkr님의 글에도 잘 정리되어 있으니 참고하자.
laravel/laravel의 composer.json을 살펴보면 다음과 같이 laravel/framework에 의존하는 것을 확인할 수 있다.
“laravel/framework”: “5.8.*” 식으로 의존하기 때문에 5.8의 최신 버전이 설치된다. 따라서 특정 버전의 라라벨을 설치하는 방법은 간단하다. laravel/laravel을 clone 혹은 내려받기한 다음 composer.json에 laravel/framework의 정확한 버전을 지정한 뒤 composer install로 설치하는 것이다.
위키의 핵심 기능은 과거의 모든 변경 내역을 조회할 수 있고, 원하면 과거 버전으로 쉽고 되돌아갈 수 있는 것이라 생각한다. 간혹 위키 같이 과거의 변경 내역을 기록으로 남기고 조회하는 기능이 필요할 때가 있다. 내가 운영하는 카페에서는 사물함 관리에 이 기능이 필요했다. 사물함 대여자 정보를 업데이트 할 때 실수를 할 수 있으므로, 변경되기 전의 데이터가 어딘가에 남아있어야 했다. 당시에는 단순하게 똑같은 테이블을 하나 더 만들어서(lockers 테이블과 똑같은 lockers_logs 테이블을 만드는 식으로) 업데이트 전에 백업하는 식으로 구현했다.
이런 기능이 내게만 필요했던 건 아니었는지 크리스 듀엘(Chris Duell)이 Revisionable이라는 패키지를 만들었다. 이 패키지를 사용하면 모델에 변화가 있을때마다 자동으로 ‘누가’, ‘무엇을’, ‘언제’, ‘어떻게’ 수정했는지가 저장된다. 수정내역을 남기고 싶은 모델에 RevisionableTrait 트레이트를 사용한다고 선언하기만 하면 된다. 사물함 관리 기능 만들 때 이 패키지가 알았더라면 ㅠ
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Venturecraft\Revisionable\RevisionableTrait;
class Post extends Model
{
use RevisionableTrait;
}
실전에서 어떻게 쓰이는지 보는게 가장 와 닿을 것 같다. 내 카페용 애플리케이션에 관심있는 사람들은 없겠지만 겸사겸사 한 번 적용해보겠다.
설치
컴포저로 설치한다.
composer require venturecraft/revisionable
설치가 완료되면 config/app.php 파일의 providers 항목에 RevisionableServiceProvider를 등록한다.
php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider"
// 제대로 진행된다면 아래와 같은 결과 메시지가 터미널에 출력된다.
Copied File [/vendor/venturecraft/revisionable/src/config/revisionable.php] To [/config/revisionable.php]
Copied Directory [/vendor/venturecraft/revisionable/src/migrations] To [/database/migrations]
Publishing complete.
Revisionable 패키지는 모든 모델의 수정내역을 하나의 테이블로 관리한다. 마이그레이션을 실행한다.
php artisan migrate
데이터가 어떻게 저장되는지 확인하기 위해 마이그레이션 파일을 살펴보자.
public function up()
{
Schema::create('revisions', function ($table) {
$table->increments('id');
$table->string('revisionable_type');
$table->integer('revisionable_id');
$table->integer('user_id')->nullable();
$table->string('key');
$table->text('old_value')->nullable();
$table->text('new_value')->nullable();
$table->timestamps();
$table->index(array('revisionable_id', 'revisionable_type'));
});
}
revisionable_type은 수정한 모델의 타입이고 revisionable_id는 수정한 모델의 ID 값이다. 이런 방식으로 하나의 테이블에서 모든 모델의 변경사항을 다루는 걸 일대다 다형성 관계라고 한다. 다형성 관계에 익숙하지 않은 사람들은 매뉴얼을 참고하자.
key는 모델의 어떤 값이 변경되었는지를 저장하는 값이다. 예를 들어 Post 모델의 title이 수정되었다면 key에 ‘title’이 값으로 저장된다.
카페용 애플리케이션에서 사물함은 Locker 모델로 관리했다. Locker 모델에 RevisionableTrait를 끼워넣는다.
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Venturecraft\Revisionable\RevisionableTrait;
class Locker extends Model
{
use RevisionableTrait;
...이하 생략
Locker를 업데이트하는 LockerService::update()는 원래 아래와 같았다.
class LockerService
{
public function update($request, $locker)
{
DB::transaction(function () use ($request, $locker) {
$this->backup($locker);
$inputs = $this->buildInputsForUpdate($request, $locker);
$locker->update($inputs);
});
}
코드는 간단하다. 백업하고 업데이트할 데이터를 준비해서 업데이트한다.
이제 Revisionable이 수정내역을 관리해주기 때문에 백업하는 코드는 더이상 필요 없다. 그래서 $this->backup($locker); 줄을 지우고 LockerService::backup() 메소드를 통째로 제거했다.
테스트로 데이터를 하나 변경해봤다. 홍길동이 대여하고 있던 사물함을 장길산이 사용하게 된 시나리오다.
‘사용자’, ‘이용 시작 시점’, ‘이용 종료 시점’, ‘비밀번호’가 바뀌었다. revisions 테이블을 보면 아래와 같이 4개의 데이터가 추가되었다.
변경 행위는 한 번인데, 변경된 필드마다 데이터가 하나씩 생성되는 점은 조금 아쉽다. 현재로서는 같은 행동이 원인이 되어 바뀌었는지 여부를 판단할 수 있는 유일한 방법은 revisions 테이블의 created_at 이 같은지 확인하는 방법 뿐이다. 수정내역을 사용자의 행동 단위로 묶을 수 있도록 식별값을 하나 더 추가했으면 어땟을까 싶다.
기본적인 기능 외에 아래와 같이 다양한 옵션을 제공한다. 필요할 때 패키지의 매뉴얼을 보고 활용하자.
전 직장에 입사했을 때, 이미 라라벨로 만든 애플리케이션이 잘 돌아가고 있었다. 다만, 테스트가 하나도 없었다. 사장님이 우선은 접속 안되는 페이지가 없는지 확인하는 테스트만 있어도 좀 안심이 될 것 같다고 이야기했고 나도 동의했다. 그때는 라우트 목록을 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월호 유료구독자를 모집하고 있습니다.
일간 이슬아와 이다님의 매일마감을 보고 용기내서 유료 뉴스레터를 시작합니다. 라라벨을 4 시절부터 써오고 있는데, 라라벨의 인기와 영향력은 날이 갈수록 늘어서 해외에서는 매일 새로운 자료들이 쏟아져 나오고 있습니다. 일 안하고 하루종일 읽기만 해도 과연 다 소화할 수 있을까 의문이 들 정도입니다. 한편, 한글로 된 자료는 좀처럼 찾아보기가 쉽지 않습니다. 그래서 매일 열심히 새로 나오는 라라벨 관련 정보들을 익히고, 하루에 한 편씩 알기 쉽게 정리해서 메일로 보내주는 일을 해보고자 합니다. 뉴스 큐레이션은 아닙니다. 샘플 원고는 여기서 확인하실 수 있습니다.
구독료는 한 달 1만원 입니다. 6월 말일까지 신청받고, 7월 1일부터 시작해서 7월 말일 까지 주중에 한 편씩 글을 보내드립니다. 주말엔 쉽니다.
보내드리는 글은 시간이 지나면 모든 사람들에게 공개할 예정입니다. 마음이 바뀌어 공개를 안할 수도 있습니다만, 일단은 3주 정도 후에 공개할까 합니다.
제 자신은 많은 개발자들이 무료로 공개한 자료로 학습했으면서, 글을 유료로 판매하는 것에 대해 다소 죄스런 마음이 있습니다. 하지만 앞서 말씀드렸듯 한글로 된 자료가 매우 희박한데, 그 이유는 지식을 공유할 만큼 여유로운 개발자의 수가 충분히 많지 않아서라고 생각합니다. 그래서 누군가는 여가시간이 아닌 일과시간을 이용해서 자료를 만들고 그것으로 돈을 버는 것도 의미가 있다고 믿습니다.
라라벨 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 클래스에도 추가했다고 합니다.
그렇지 않아도 이번에 막 알림을 메일로도 받을 수 있게 작업하려는 참이었는데 잘됐네요 ㅎㅎ