본문으로 건너뛰기

Laravel 5.5 - 다형성 관계

· 약 6분

댓글 테이블이 있고 이 댓글은 여러 테이블에서 사용된다고 치자. 그럼 댓글 테이블에 type과 type_id를 가져가야할 것이다. 이 때 사용할 수 있는 관계가 다형성 관계(릴레이션)인데, 공식 문서의 설명이 조금은 부족하다고 느꼈다. 파헤쳐보자.

morphTo는 type과 type_id를 가진, 여러 테이블로 연결되어야할 테이블에서 사용하는 릴레이션 메소드이다. 공식 문서에는 데이터를 가져온 뒤 릴레이션을 연결하는 예시만 있고, Eager 로딩 (With 구문을 사용하는 방법) 후 specific한 필드를 사용하게 변경하는 경우에 대한 정보는 없다.

기본 문법

YourModel.php
<?php

class YourModel extends Model {
...

// 다형성 관계를 가질 함수를 data로 정의했다
public function data() {
return $this->morphTo();
}
}

이렇게 정의시에 YourModel::with('data')->get() 으로 호출하면 불러와져야되지만, 필드명, 모델명이 정확하지 않으면 쿼리 호출조차 되지 않는다. (심지어 에러도 발생하지 않는다)

필드명 정의

먼저 morphTo의 소스코드를 를 살펴보자.

morphTo
<?php
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $name
* @param string $type
* @param string $id
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null)

name, type, id를 파라미터로 받는다. 그럼 파라미터를 넘겨보자.

YourModel.php
<?php

class YourModel extends Model {
...

public function data() {
// morphTo의 paremeter로 null, 타입필드명, 타입인덱스 필드명을 넘긴다.
return $this->morphTo(null, 'type', 'type_idx');
}
}

여기서 name엔 도대체 뭘 넣어야 되는거야? 라고 의문이 생길 수가 있다. 함수 내에서 name 변수는 $this->getMorphs(Str::snake($name), $type, $id); 에만 딱 한 번 사용된다.

getMorphs 함수를 따라가보자.

getMorphs
<?php
protected function getMorphs($name, $type, $id) {
// $type과 $id가 명시되면 그 값을 먼저 반환한다.
return [$type ?: $name.'_type', $id ?: $name.'_id'];
}

주석처럼 typeid가 명시되면 name값은 사용되지 않는 쓰레기 값이 되어버린다. 따라서 null로 넘겨주면 된다.

타입-모델 바인딩

타입과 인덱스를 명시하면 드디어 오류메세지가 노출된다. 내가 정의한 type명을 가진 Class가 없다 라는 내용인데, 이제 타입과 모델을 연결시켜보자.

이 때 사용할 수 있는 메소드가 공식 문서에서 조금 스크롤을 내리면 있는 Custom Polymorphic Types에 잘 설명되어 있다.

하지만 등록하는 부분에 대한 설명이 **You may register the morphMap in the boot function of your AppServiceProvider or create a separate service provider if you wish.**라고 되어있다. 즉 AppServiceProvider에 넣던지 Service Provider로 생성이다.

한 모델에만 쓸 건데 전체에 등록을 할 필요가 없으니, 사용할 모델에 기능을 넣어보자.

YourModel.php
// Relation을 사용해야한다.
use Illuminate\Database\Eloquent\Relations\Relation;

<?php
class YourModel extends Model {
// 이 메소드는 모델이 initialize될 때 실행된다.
protected static function boot() {
parent::boot();

// 여기에 타입 별로 모델을 바인딩한다.
Relation::morphMap([
// type이 product일 경우 id는 product_id를 가리킨다.
'product' => 'App\Models\Product',
// type이 order일 경우 id는 order_id를 가리킨다.
'order' => 'App\Models\Order'
]);
}

public function data() {
// morphTo의 paremeter로 null, 타입필드명, 타입인덱스 필드명을 넘긴다.
return $this->morphTo(null, 'type', 'type_idx');
}
}

완벽해졌다. 이제 오류 없이 실행되는 것을 확인할 수 있다.

morphOne

문서 상에는 설명 되지 않은 morphOne 이란 메소드도 있다. morphMany는 관계가 설정된 값을 배열로 반환하지만 morphOne은 하나의 데이터로 반환한다. (hasOne과 hasMany처럼)

구조는 다음과 같다.

<?php
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)

morphToMany

morphedByMany

여담

다대다 다형성 관계 메소드 (morphToMany, morphedByMany)의 경우는 나중에 사용하게 되면 정리해야겠다. Relation 메소드들은 문서를 대충 훑고 API Docs를 직접 까보는게, 효율적인 것 같다.

Laravel 5.5 - Multi DB Connection

· 약 2분

여러 데이터베이스에서 데이터를 가져와야되는 경우가 있다. (마이그레이션 또는 발송 모듈 DB의 분기 등등) 라라벨에선 아주 쉽게 설정이 가능하다.

config/database.php에 새로운 커넥션 정보를 넣어주자. 새 커낵션은 mysql_new 라고 이름지었다.

config/database.php
<?php
return [
...
'connections' => [
// 기본 커넥션
'mysql' => [
...
],
// 새 친구
'mysql_new' => [
'driver' => 'mysql',
'host' => '111.111.111.111',
'port' => '3306',
'database' => 'test',
'username' => 'test',
'password' => 'test1234',
'unix_socket' => '',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
'prefix' => null,
'engine' => null
],
],
];

모델

새 커넥션에 사용할 모델을 만들어주고, 모델에서 연결할 커넥션을 설정해주자.

model.php
<?php
...
class OldMember extends Model
{
// 커넥션 변수를 다시 설정해주면 끝
protected $connection = 'mysql_new';
protected $table = 'test';
...
}

사용

기존 모델 사용법과 똑같다. 아주 간단하다.

쉘 스크립트 if 조건변수

· 약 2분

쉘 스크립트의 비교 변수 몇 가지를 알아보자.

연산자기능예시
-afile_exists[ -a /etc/passwd ]
-dfile_exists && is_dir[ -d /etc ]
-ffile_exists && is_file[ -f /etc/passwd ]
-sfile_exists && not empty[ -s /etc/passwd ]
-w해당 유저로 쓰기 가능[ -w test.txt ]
-x해당 유저로 실행 가능[ -x test.sh ]
-N마지막 파일 읽은 시점부터 변경점이 있는지[ -N test.txt ]
-O해당 유저의 파일인지[ -O test.txt ]
-G해당 그룹의 파일인지[ -G test.txt ]
-ntB파일보다 A파일이 새로운지[ A_file -nt B_file ]
-otB파일보다 A파일이 오래됬는지[ A_file -ot B_file ]

숫자 비교

연산자기능예시
-ltLess than[ 0 -lt 1 ]
-leLess than or Equal[ 1 -le 1 ]
-eqEqual[ 1 -eq 1 ]
-gtGreater than[ 1 -gt 0 ]
-geGreater than or Equal[ 1 -ge 1 ]
-neNot Equal to[ 1 -ne 0 ]

number_format의 반대 함수

· 약 1분

number_format 으로 쉽게 comma 가 들어간 숫자를 만들 수 있는데, 이 반대 방법은 preg_replace 를 통해 comma 를 제거한 뒤에 다시 int 로 형변환을 해야한다.

더 간단하게 변경할 수 있는 방법은 바로 filter_var를 사용하는 것이다.

<?php

$formatted_nubmer = number_format(10000);

echo $formatted_number; // 10,000;

$number = filter_var($formatted_nubmer, FILTER_SANITIZE_NUMBER_INT);
// FILTER_SANITIZE_NUMBER_INT 상수가 너무 길어서 외우기가 힘들다면 519
$number2 = filter_var($formatted_nubmer, 519);

echo $number; // 10000;
echo $number2; // 10000;

필터 옵션

필터 상수에 대해 궁금해졌다면 공홈을 참조하자.

Docker와 Docker-compose 제대로 설치하기

· 약 2분

구글링하면 너무 예전 버전 (1버전 대)의 설치방법만 나와있다.

영문을 따라할 수 있으면 공홈을 보고하면 된다.

옛 버전 삭제

$ sudo yum remove docker \
docker-common \
docker-selinux \
docker-engine

서비스를 내리고 docker를 삭제해도 /var/lib/docker/ 폴더는 지워지지 않고 여기에 기존 데이터가 모두 남아있다.

필수 패키지 설치

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# docker repo를 등록한다.
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# yum package를 업데이트
$ sudo yum makecache fast

다운로드

$ sudo yum install docker-ce

실행

$ sudo systemctl start docker
$ sudo systemctl enable docker

$ sudo docker --version
Docker version 17.06.0-ce, build 02c1d87

Docker-compose 설치

다운로드

# root로 로그인해야한다.
$ curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

실행 권한 부여

$ chmod +x /usr/local/bin/docker-compose

# 설치 확인
$ docker-compose --version
docker-compose version 1.14.0, build 1719ceb

설치가 완료되었다.

Centos6에서 설치하기

centos6 버전에서는 위의 설치방법으로 Docker를 설치할 수 없다. (RHEL7 버전 전용이기에) 다음과 같이 설치하자.

$ yum install http://mirrors.yun-idc.com/epel/6/i386/epel-release-6-8.noarch.rpm

$ yum install -y docker-io

$ service docker start

$ chkconfig docker on

Docker Error response from daemon: reference does not exist

· 약 1분

Docker rmi명령어로 이미지를 삭제하는데 Error response from daemon: reference does not exist 오류가 나면서 이미지 삭제가 안 되는 경우 다음과 같이 하면된다.

구글링하면 다시 설치하거나 cache를 비우거나 하라는데 해결되진 않았고 쉽게 접근하면 된다. 그냥 이미지 폴더를 날리자

$ sudo systemctl stop docker
$ sudo rm -rf /var/lib/docker
$ sudo systemctl start docker

Linux 폴더 구조

· 약 2분

윈도우도 Program Files 가 무슨 폴더인지 보자마자 알듯 리눅스도 폴더명이 친숙해지면 더 쉬워지지 않을까 했다. (보통 /home, /etc, /var, /tmp 정도만 왔다 갔다 하니까) 이 정도만 정리해 놓으면 CPU 정보를 볼때 왜 /proc/cpuinfo를 까야되는지 한 번에 감이 올 것 같다.

경로설명
/Root
/bin기본 명령어 프로그램 (ex. ls, cp)
/dev장치 파일
/etc시스템 설정 및 사용자 정보, 네트워킹 설정 파일
/home유저 폴더
/lib실행파일이 사용할 수 있는 공유 라이브러리 코드 파일
/proc시스템 통계
/sys장치와 시스템 인터페이스 제공
/sbin시스템 실행 파일
/tmp임시 파일
/usr리눅스 시스템 파일 (사용자 파일이 아님)
/var변수 서브디렉토리로 프로그램 런타임 정보 기록
/boot커널 부트 로더 파일
/media제거 가능한 미디어를 위한 마운트 포인트
/opt제3자 소프트웨어 파일
/usr/includeC컴파일러 헤더파일
/usr/infoGNU 정보 매뉴얼
/usr/local관리자 소프트웨어 설치 파일
/usr/man매뉴얼 페이지
/usr/share다른 유닉스 머신과 작업해야 할 파일
/vmlinuz 또는 /boot/vmlinuz리눅스 커널

여담

Linux Kernel을 이해하는 그 날까지

Laravel - Log::debug vs logger

· 약 1분

로그를 찍기 위해서 Log 파사드 또는 logger 헬퍼 함수를 사용할 수 있다.

두 함수의 차이점은 무엇일까?

  • Log 파사드가 정의되어있어야 한다. use Log;
  • NULL 값이 들어올시 NULL이 로그에 출력된다.

logger

  • 헬퍼함수라 Log 파사드 없이 사용 가능하다.
  • NULL 값이 들어올시 아무 내용도 찍히지 않는다.
  • debug 레벨이 기본이라, 다른 레벨의 로그를 찍으려면 logger()->error('에러 로그'); 처럼 함수를 하나 더 호출해야한다.
  • 단, info 레벨 로그는 info(); 헬퍼 함수를 사용할 수 있다.

Laravel 5.5 - 일본으로 메일 보내기

· 약 3분

일본 통신사 중 소프트뱅크와 AU는 UTF8 메일이 깨져서 들어간다. (도코모는 정상적) Laravel 뿐아니라 일본으로 메일을 보내고 싶다면, 이 포스팅의 방식대로 접근하면 된다.

먼저 인코딩을 iso-2022-jp로 바꿔줘야한다.

테스트 결과 JIS 인코딩과 같다. SJIS 인코딩도 있는데 위 인코딩에 몇가지 특수문자가 추가된 형태이다.

AppServiceProvider에 메일 인코딩을 전역으로 설정하자.

app/Providers/AppServiceProvider.php
<?php
...
use Swift;
use Swift_DependencyContainer;
use Swift_Preferences;

class AppServiceProvider extends ServiceProvider
{
...
public function register() {
// laravel의 메일 패키지는 Swift이므로 당황하지말자
Swift::init(function() {
// 헤더를 추가하고
Swift_DependencyContainer::getInstance()
->register('mime.qpheaderencoder')
->asAliasOf('mime.base64headerencoder');
// 인코딩을 변경한다.
Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
});
}
}

7bit

위키를 참조해보면 iso-2022는 문자열을 7bit 또는 8bit로 표현하는 기술이다. 근데, 소프트뱅크 문서에 7bit로 달란다. 메일 폼을 열어서 build시에 인코딩 바이트를 변경하자.

app/Mail/YourMailForm.php
<?php
...
use Swift_Mime_ContentEncoder_PlainContentEncoder;

class YourMailForm extends Mailable
{
...
public function build() {
return $this->subject('ご注文を承りました。')
->view('mail.your_mail_view')
->withSwiftMessage(function($message) {
// 전역으로 설정한 뒤에 이 메소드를 빼보고 테스트를 못 해봤다.
// 다음 분이 빼고 테스트 부탁드려요 : )
$message->setCharset('iso-2022-jp')
// 인코딩 바이트를 바꿔준다.
->setEncoder(new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit'));
});
}
}

mail.view

가장 중요한 것은 Mail View에서도 charset meta tag를 등록해줘야 한다는 것이다. 이 걸 세팅안하고 얼마나 삽질을 해댔는지, 없던 이두근이 생길지경.

resources/view/mail/your_mail_view.blade.php
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-2022-jp">
</head>
<body>
...
</body>
</html>

이렇게 세팅을 하고 메일을 보내면 정상적으로 보내지는 걸 확인할 수 있다.

여담

세 가지만 기억하자.

  • iso-2022-jp
  • 7bit
  • meta[charset="iso-2022-jp"]

AWS 용어 정리

· 약 2분

AWS의 생태계에 들어가기 위해선 먼저 용어에 익숙해져야 될 것 같았다. 사용법은 쉬운데.. 약어가 처음 접하는 사람에게 조금 부담스러웠다.

서비스명약어설명
Elastic Compute CloudEC2클라우드 서버
EC2 Container ServiceECSDocker 컨테이너 서버
Elastic Load BalancingELB트래픽, 부하 분산 서비스
Lambda-클라우드 Function
Scale Up-인스턴스 물리용량 증가
Scale Out-인스턴스 복제
Auto Scaling-인스턴스 Scale 자동화
Route 53-DNS 서비스
Virtual Private CloudVPC내부망 구축 서비스
Relational Database ServiceRDSRDBMS 구축 서비스
DynamoDB-NoSQL 구축 서비스
Simple Storage ServiceS3데이터 스토리지 서비스 (파일서버)
CloudFront-CDN 서비스
Glacier-저렴한 스토리지 서비스 (주로 백업용)
Elastic Block StoreEBSEC2의 가상 하드디스크
Identity and Access ManagementIAM인증, 권한 부여
CloudWatch-AWS 리소스 모니터링
Elastic BeanstalkEBPaaS
LightSail-저렴한 EC2 (VPS)

여담

전체적으로 GCP보다 용어가 직관적이지 않은 느낌 (App Engine, Compute Engine을 보면 얼마나 멋진가)이였는데, 풀어 놓고 적어보니 GCP도 AE, CE 이렇게 적으면 못 알아 먹었겠구나.