정확한 공지는 Custom domains on GitHub Pages gain support for HTTPS를 참고.
현재 이 블로그도 Github Pages를 사용하고 있다.
"블로그를 리뉴얼 하다"에서 언급했던 것 처럼 Octopress에서 Jekyll을 거쳐 직접 개발한 정적 파일 기반 블로그를 사용하고 있으니 무료에 트래픽도 넉넉한 Github Pages 만한 것도 없다(GitHub Pages에 대한 제약사항은 What is GitHub Pages?에서 확인할 수 있다).
게다가 git에 의한 버전관리까지.
그런데 사실 이 블로그도 얼마전까지는 Firebase 호스팅 을 통해 서비스하고 있었다. Firebase는 무료의 경우 개설 프로젝트의 개수에 제한이 있고, 호스팅의 경우도 용량과 트래픽에 제한이 있지만 HTTPS와 전세계에 퍼져있는 CDN을 무료로 사용할 수 있기 때문이다.
Firebase 호스팅을 사용하기 전에는 Github Pages + Cloudflare를 통해서 무료 HTTPS + CDN을 사용했다(Github Pages에서 HTTPS 를 지원하지 않았기 때문에 Cloudflare의 무료 SSL을 사용했음).
그런데 "클라우드플레어(Cloudflare)가 한국에서 느린 이유"에 나오는 것처럼 속도가 너무 느려 Firebase 호스팅으로 옮겼다. 단, 개인적으로 Firebase 호스팅은 배포가 좀 불편해서 불만이 있었다(편리한 CLI를 제공하지만 그냥 웬지 불편했다).
그렇게 지내던 중 Github Pages에서 HTTPS 지원을 시작했다는 소식을 접하고 급하게 블로그와 기타 웹페이지들을 다시 이전했다.
결과는 대만족. 속도도 빠르고 HTTPS 설정도 아주 간단하다.
이전하던 웹페이지들 중 하나는 아직 Firebase에 있는데 그 이유는 그 웹페이지 도메인만 SSL 생성이 완료되지 않는 상황에 부딪쳤기 때문이다. 이미 서비스 중인 웹페이지라 SSL 오류가 나는 상태로 계속 둘 순 없었다.
Github 고객센터에 안되는 영어로 문의를 남겼으나 아직 해결하진 못했다. 빠르게 답변을 받았으나 내가 DNS 설정을 미리 바꿔두지 않는한 자신들이 확인할 수가 없다는 것이었다. 그래서 현재는 도메인을 아예 이전하는 방법을 고민 중이다(그 정도로 Firebase에서 Github로 옮기고 싶다).
"개발자스럽다" 님의 "Github Pages 개인 도메인도 무료로 HTTPS 지원 시작!"에 보면 이 소식과 함께 적용 방법을 잘 설명해 두셨다.
한가지 보강하자면 "A 레코드의 IP를 변경"하는 방법을 설명하고 있지만 CNAME을 설정(USERNAME.github.io)하는 것이 향후 IP 변경에 유연하므로 더 좋다. 요즘엔 대부분의 DNS 서비스에서 CNAME을 지원한다.
]]>나의 경우는 예전엔 컴파일 방법을 주로 사용했었으나 RPM 기반으로 설치하는 것이 더 편리하기도 하고 성능 최적화에 유리하다는 소식을 접하고 yum을 주로 사용하고 있다(yum을 사용해서 PHP를 설치할 경우 CentOS에 포함된 PHP의 버전은 매우 낮기 때문에 외부 RPM repository를 사용하는 경우가 많다).
PHP 최신 버전을 제공하는 외부 repository 중 유명한 곳은 webtatic과 remi 등이 있다. 이중에서 webtatic을 주로 사용하다가 이번에 PHP 7.2로 버전업하는 과정에서 remi로 갈아탔다. 내가 remi를 선택한 이유는 다음과 같다.
위에 나열한 이유는 크게 중요한 문제들은 아니며 대안이 존재하지만 간편한 사용을 위해 갈아탔다. 사실 굳이 다른 repository를 알아봤던 이유는 mcrypt 때문이다. 보안상의 문제로 사용이 권장되지 않는 모듈이지만 PG(결제대행)사에서 제공하는 모듈이 mcrypt를 필수로 요구하고 있는 상황이라 사용하지 않을 수 없었다.
사설이 길었는데 이제 본격적으로 설치에 대해 알아보자(yum을 이용한 설치는 무척 간단하다).
$ wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ rpm -Uvh epel-release-latest-7.noarch.rpm
$ wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm
$ rpm -Uvh remi-release-7.rpm
$ yum install -y yum-utils
$ yum-config-manager --enable remi-php72
repository 주소는 OS/Version 별로 차이가 있다. 다른 OS를 사용하고 있거나 자세한 내용을 확인하고 싶다면 Remi's RPM repository를 참고하자.
$ yum remove -y `yum list installed | cut -d " " -f 1 | grep php`
위 명령을 실행하기 전에 yum list installed | cut -d " " -f 1 | grep php
만 먼저 실행해서 삭제될 패키지를 확인하는 것이 좋다(잘못된 패키지가 삭제되지 않도록).
$ yum install -y php-common php-fpm php-cli \ php-process \ php-opcache php-pecl-apcu \ php-mysqlnd php-pdo \ php-gd \ php-mbstring php-xml \ php-pecl-zip \ php-bcmath
"php-common" 외의 패키지는 자신의 상황에 맞게 조정해서 설치한다.
"php-common" 대신 "php72w-common"과 같이 PHP 버전을 지정한 패키지를 사용해도 된다. 항상 최신 버전을 사용할 것이 아니라 특정 버전대를 사용해야 한다면 이 방법을 사용하자. 향후 PHP가 버전업되면 운영중인 프로그램과의 호환에 문제가 발생할 수 있으므로 이 방법이 더 안전하다. 단, 메이저 버전업시 기존 패키지를 지우고 설치하는 방법을 사용해야 하므로 불편하다.
기존에 yum으로 설치된 PHP가 존재하고 해당 패키지의 이름이 "php-"로 시작한다면(위에 설명한 것과 같이 "php72w-"로 시작하는 패키지가 설치된 경우가 아닌) 기존 패키지를 지우고 재설치하는 것보다 아래처럼 그냥 update 받는 방법도 있다.
$ yum update php-*
나의 경우는 기존에 webtatic을 사용했기 때문에 패키지들의 이름이 "php70w-"로 시작해서 기존 패키지를 지우고 새로 설치했다. 대신, 이번에 "php-"로 시작하는 패키지들을 설치했기 때문에 향후 버전업 시에는 update 방식을 사용할 수 있다.
$ php -v PHP 7.2.2 (cli) (built: Jan 30 2018 13:44:49) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies
이것으로 PHP 7.2 버전의 설치가 끝났다.
yum을 이용한 PHP의 설치는 앞서 보는 것과 같이 아주 간단하다. "php-" 패키지를 설치할지 "php72w-" 패키지를 설치할지는 자신의 관리 성향에 맞추어 잘 선택하길 바란다(생각보다 중요한 문제다).
]]>두가지 지원 사항 중 하나인 ACME는 위키피디아에서 아래와 같이 설명하고 있다.
The Automatic Certificate Management Environment (ACME) protocol is a communications protocol for automating interactions between certificate authorities and their users' web servers, allowing the automated deployment of public key infrastructure at very low cost.[1][2] It was designed by the Internet Security Research Group (ISRG) for their Let's Encrypt service.
ACME v2 protocol에 대해서는 명확한 설명을 찾지 못했지만 이 링크를 통해 비교해 볼 수는 있다.
사실 그냥 무료 인증서가 필요해서 사용하는 입장에서는 ACME protocol의 상세는 중요하지 않다고 본다.
이번 업데이트에서 가장 중요한 부분은 와일드카드 SSL 인증서를 지원한다는 것이다.
기존에는 SSL을 사용하고자 하는 모든 도메인을 일일이 세팅해 주었어야 하지만 와일드카드를 지원하게 됨으로써 하나의 SSL 인증서를 발급받아 여러 서브 도메인에서 사용할 수 있게 된다.
업데이트 이전에는 해당 도메인에 접속하여 인증한 후 SSL 인증서를 발급하는 방식이었다. 하지만 와일드카드를 지원하게 되면 발급 과정이 어떻게 변경되는지 궁금해서 찾아보았으나 아직 찾지는 못했다.
기존에 주로 certbot을 이용해 발급/갱신 했었기 때문에 해당 사이트도 대충 둘러 보았으나 찾지 못했다(1월 4일까지 공개 테스트를 한다고 했기 때문에 어딘가엔 방법이 있을 듯한데).
어쨌든 2월 27일이되면 상세한 설명이 나올 것으로 보이니 기다려봐야 겠다.
결론적으로 와일드카드 SSL을 무료로 사용할 수 있게 된다니 아주 기대된다.
]]>넷스케이프(Netscape)에서 1995년 개발한 자바스크립트(javascript)는 웹 브라우저에서 동적인 기능을 제공하기 위한 언어다. 현재는 대부분의 브라우저에서 이 언어를 제공하고 있다. 그런데 표준 규격없이 여러 브라우저에서 독자적인 특성이 추가되면서 호환성 문제가 발생하기 시작했다. 이에 ECMA 국제 기구에서 “ECMAScript Standard”라는 표준을 만들게 되었다. 정확히 이야기 하자면 현재의 자바스크립트는 ECMAScript와 BOM(Browser Object Model)와 DOM(Document Object Model)을 포괄하는 개념이다.
개별적인 설명에 앞서 개발자가 필히 알아야 할 ES6 10가지 기능을 나열하자면 아래와 같다.
이제부터 하나씩 알아보자.
var link = function (height, color, url) {
var height = height || 50
var color = color || 'red'
var url = url || 'http://azat.co'
...
}
함수에 넘겨주는 인자값에 대한 default 처리를 위해 위와 같이 처리 했었다면 ES6에서는 아래와 같이 간단히 처리할 수 있다.
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
...
}
단, 주의해야 할 점이 있다. 인자값으로 0 또는 false가 입력될 때 두 예시의 결과는 다르다. ES5에서는 ||
처리 시 0 또는 false 값이 입력 되어도 거짓이 되므로 기본값으로 대체된다. 하지만 ES6의 기본 매개 변수를 사용하면 undefined 를 제외한 입력된 모든 값(0, false, null 등)을 인정한다.
ES5에서는 아래와 같이 문자열을 처리해야 했다.
var name = 'Your name is ' + first + ' ' + last + '.'
var url = 'http://localhost:3000/api/messages/' + id
하지만 ES6에서는 템플릿 리터럴을 제공하므로 "`" (back-ticked) 문자열 안에 ${NAME}
라는 새로운 구문을 사용해서 아래와 같이 간단히 처리할 수 있다.
var name = `Your name is ${first} ${last}.`
var url = `http://localhost:3000/api/messages/${id}`
ES5에서는 멀티 라인 문자열을 처리하기 위해 아래와 같은 방법들을 사용해야 했다.
var roadPoem = 'Then took the other, as just as fair,\n\t'
+ 'And having perhaps the better claim\n\t'
+ 'Because it was grassy and wanted wear,\n\t'
+ 'Though as for that the passing there\n\t'
+ 'Had worn them really about the same,\n\t'
var fourAgreements = 'You have the right to be you.\n\
You can only be you when you do your best.'
하지만 ES6에서는 "`" (back-ticked) 문자열을 이용해서 아래와 같이 간단히 처리할 수 있다.
var roadPoem = `Then took the other, as just as fair,
And having perhaps the better claim
Because it was grassy and wanted wear,
Though as for that the passing there
Had worn them really about the same,`
var fourAgreements = `You have the right to be you.
You can only be you when you do your best.`
ES5에서는 구조화된 데이터를 변수로 받기 위해 아래와 같이 처리해야 했다.
// browser
var data = $('body').data(), // data has properties house and mouse
house = data.house,
mouse = data.mouse
// Node.js
var jsonMiddleware = require('body-parser').json
var body = req.body, // body has username and password
username = body.username,
password = body.password
하지만 ES6에서는 비구조화 할당을 사용해 아래와 같이 처리할 수 있다.
var {house, mouse} = $('body').data() // we'll get house and mouse variables
var {jsonMiddleware} = require('body-parser')
var {username, password} = req.body
주의할 점은 var로 할당하려는 변수명과 구조화된 데이터의 property명이 같아야 한다. 또한 구조화된 데이터가 아니라 배열의 경우 {}
대신 []
를 사용해서 위와 유사하게 사용할 수 있다.
var [col1, col2] = $('.column'),
[line1, line2, line3, , line5] = file.split('\n')
ES5에서는 아래와 같이 JSON을 사용해서 객체 리터럴을 만들 수 있었다.
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountServiceES5 = {
port: serviceBase.port,
url: serviceBase.url,
getAccounts: getAccounts,
toString: function() {
return JSON.stringify(this.valueOf())
},
getUrl: function() {return "http://" + this.url + ':' + this.port},
valueOf_1_2_3: getAccounts()
}
위 예시와 달리 serviceBase를 확장하길 원한다면 Object.create
로 프로토타입화하여 상속 받을 수 있다.
var accountServiceES5ObjectCreate = {
getAccounts: getAccounts,
toString: function() {
return JSON.stringify(this.valueOf())
},
getUrl: function() {return "http://" + this.url + ':' + this.port},
valueOf_1_2_3: getAccounts()
}
accountServiceES5ObjectCreate.__proto__ = Object.create(serviceBase)
accountServiceES5ObjectCreate와 accountServiceES5는 동일하게 사용할 수 있으나 다른 구조를 가진다. accountServiceES5ObjectCreate는 accountServiceES5와 다르게 __proto__
에 port
와 url
속성을 가진 객체를 담고 있다.
ES6에서는 아래와 같이 처리할 수 있다.
var serviceBase = {port: 3000, url: 'azat.co'},
getAccounts = function(){return [1,2,3]}
var accountService = {
__proto__: serviceBase,
getAccounts,
toString() {
return JSON.stringify((super.valueOf()))
},
getUrl() {return "http://" + this.url + ':' + this.port},
[ 'valueOf_' + getAccounts().join('_') ]: getAccounts()
};
위 예시에 대해 ES5와의 차이를 요약하면 아래와 같다.
__proto__
속성을 사용해서 바로 프로토타입을 설정할 수 있다.getAccounts: getAccounts,
대신 getAccounts,
를 사용할 수 있다 (변수명으로 속성 이름을 지정).[ 'valueOf_' + getAccounts().join('_') ]
와 같이 동적으로 속성 이름을 정의할 수 있다.조금 더 자세한 내용을 보고 싶다면 gsfe/es2015features 를 참고하자.
화살표 함수는 항상 익명 함수이며 this
의 값을 현재 문맥에 바인딩 시킨다.
아래의 예시는 화살표 함수가 지원되지 않는 ES5에서 this
를 사용하기 위한 처리 예시다.
var _this = this
$('.btn').click(function(event){
_this.sendData()
})
다음은 위 예시를 화살표 함수로 대체한 ES6 예시이다.
$('.btn').click((event) => {
this.sendData()
})
다음은 ES5에서 call
을 사용하여 context
를 logUpperCase()
함수에 전달하는 또 다른 예제다.
var logUpperCase = function() {
var _this = this
this.string = this.string.toUpperCase()
return function () {
return console.log(_this.string)
}
}
logUpperCase.call({ string: 'es6 rocks' })()
ES6에서는 화살표 함수를 사용하면 _this
를 사용할 필요가 없다.
var logUpperCase = function() {
this.string = this.string.toUpperCase()
return () => console.log(this.string)
}
logUpperCase.call({ string: 'es6 rocks' })()
화살표 함수가 한 줄의 명령문과 함께 사용되면 표현식이 되어 명령문의 결과를 암시적으로 반환한다.
ES5에서의 처리 예시.
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(function (value) {
return "ID is " + value // explicit return
});
ES6에서의 처리 예시.
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map(value => `ID is ${value}`) // implicit return
여러 개의 인자를 사용하는 경우는 변수 목록을 ()
로 감싸줘야 한다.
ES5에서의 처리 예시.
var ids = ['5632953c4e345e145fdf2df8', '563295464e345e145fdf2df9'];
var messages = ids.map(function (value, index, list) {
return 'ID of ' + index + ' element is ' + value + ' ' // explicit return
});
ES6에서의 처리 예시.
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `) // implicit return
또한 본문을 괄호로 감싸 객체 표현식을 반환할 수 있으며 ...
을 이용해 가변 파라미터를 사용할 수도 있다.
var ids = ['5632953c4e345e145fdf2df8','563295464e345e145fdf2df9']
var messages = ids.map((value, index, ...abc) => ({v:value, i:index, a:abc}))
ES6에서는 표준 Promise가 제공된다.
아래는 setTimeout
을 이용한 지연된 비동기 실행에 대한 ES5 예시다.
setTimeout(function(){
console.log('Yay!')
}, 1000)
위 예시를 ES6에서 Promise를 사용해서 재작성하면 아래와 같다.
var wait1000 = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000)
}).then(function() {
console.log('Yay!')
})
위 예시를 화살표 함수를 사용해 재작성한 예시는 아래와 같다.
var wait1000 = new Promise((resolve, reject)=> {
setTimeout(resolve, 1000)
}).then(()=> {
console.log('Yay!')
})
ES5 보다 ES6의 Promise를 사용한 예시가 더 복잡해 보이지만 아래와 같이 중첩된 setTimeout
예시를 보면 Promise의 이점을 확인할 수 있다.
setTimeout(function(){
console.log('Yay!')
setTimeout(function(){
console.log('Wheeyee!')
}, 1000)
}, 1000)
아래는 ES6 Promise 로 작성된 예시.
var wait1000 = ()=> new Promise((resolve, reject)=> {setTimeout(resolve, 1000)})
wait1000()
.then(function() {
console.log('Yay!')
return wait1000()
})
.then(function() {
console.log('Wheeyee!')
});
조금 더 자세한 내용을 보고 싶다면 Introduction to ES6 Promises – The Four Functions You Need To Avoid Callback Hell 또는 gsfe/es2015features 를 참고하자.
let과 const는 중괄호("{}")로 정의된 블록으로 유효 범위(스코프)를 지정하는 새로운 var이다. 단, let은 변수를 const는 상수를 선언한다.
function calculateTotalAmount (vip) {
var amount = 0
if (vip) {
var amount = 1
}
{ // more crazy blocks!
var amount = 100
{
var amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
위 예시의 결과는 1000 이다. var는 전역 또는 함수 내부로 유효 범위를 갖기 때문에 예시에 사용된 함수 내부의 "{}" 들은 아무런 역할을 하지 못한다. 아래는 위 예시에서 var를 let으로 바꾼 ES6 예시다.
function calculateTotalAmount (vip) {
var amount = 0 // probably should also be let, but you can mix var and let
if (vip) {
let amount = 1 // first amount is still 0
}
{ // more crazy blocks!
let amount = 100 // first amount is still 0
{
let amount = 1000 // first amount is still 0
}
}
return amount
}
console.log(calculateTotalAmount(true))
이 예시의 결과는 0 이다. let 으로 선언된 변수는 "{}" 블록 내부로 유효 범위가 한정되므로 100, 1000으로 할당된 변수는 해당 블록 내부에서만 유효하기 때문이다. if
블록 내부에서 let으로 선언된 amount 또한 해당 if
블록 내에서만 유효하므로 아무런 변경이 일어나지 않는다.
아래의 예시는 const를 사용한 예시다. const는 상수를 선언하는 것으로 여러번 선언될 수 없지만 let과 같이 블록 내부로 유효 범위가 한정되므로 아래의 예시는 오류가 발생하지 않는다.
function calculateTotalAmount (vip) {
const amount = 0
if (vip) {
const amount = 1
}
{ // more crazy blocks!
const amount = 100
{
const amount = 1000
}
}
return amount
}
console.log(calculateTotalAmount(true))
ES6에는 class 키워드가 추가되어 ES5의 prototype 기반 상속보다 명확하게 class를 정의할 수 있다. get
과 set
키워드 외에도 static
키워드를 사용해 static 메소드를 정의하는 것도 가능하다.
class baseModel {
constructor(options = {}, data = []) { // class constructor
this.name = 'Base'
this.url = 'http://azat.co/api'
this.data = data
this.options = options
}
getName() { // class method
console.log(`Class name: ${this.name}`)
}
}
constructor 는 class 내부에 하나만 존재할 수 있으며 메소드 정의에 function 또는 콜론(":")이 더이상 필요하지 않다. 단, property의 경우 메소드와 달리 생성자에서 값을 할당해야 한다.
또한 아래의 예시와 같이 class NAME extends PARENT_NAME
형식으로 상속이 가능하다. 상속시 부모 생성자를 호출하기 위해 super()
를 사용할 수 있다. 생성자가 아닌 메소드에서는 super
키워드를 사용해서 부모 메소드에 접근한다.
class AccountModel extends baseModel {
constructor(options, data) {
super({private: true}, ['32113123123', '524214691']) //call the parent method with super
this.name = 'Account Model'
this.url +='/accounts/'
}
get accountsData() { //calculated attribute getter
// ... make XHR
return this.data
}
}
class 는 get
과 set
키워드를 사용할 수 있으며 선언된 함수는 아래와 같이 사용할 수 있다.
let accounts = new AccountModel(5)
accounts.getName()
console.log('Data is %s', accounts.accountsData)
위 예시를 실행하면 아래와 같은 결과를 얻을 수 있다.
Class name: Account Model
Data is %s 32113123123,524214691
ES6 에서 모듈을 공식적으로 제공하기 전까지는 CommonJS, AMD, RequireJS 등의 비공식 모듈 스펙을 사용해 왔다. ES6에서 제공하는 모듈 스펙은 기존과 유사하지만 차이가 있다.
ES5에서 CommonJS를 이용해서 모듈을 사용하는 예시는 아래와 같다(module.js).
module.exports = {
port: 3000,
getAccounts: function() {
...
}
}
main.js 파일에서 위에서 정의한 모듈을 불러서 사용하는 예시는 아래와 같다.
var service = require('module.js')
console.log(service.port) // 3000
여기서 부터는 ES6의 import
와 export
를 사용해서 유사한 기능을 구현한 예시다(module.js).
export var port = 3000
export function getAccounts(url) {
...
}
main.js 파일에서는 import
를 사용해서 module.js 모듈을 불러올 수 있다.
import {port, getAccounts} from 'module'
console.log(port) // 3000
위와 유사하지만 export
된 모든 변수를 아래와 같이 하나의 구조화된 데이터로 받을 수도 있다.
import * as service from 'module'
console.log(service.port) // 3000
ES6는 확정되었지만 아직 모든 브라우저에서 완전하게 지원되지 않는다. 따라서 지금 당장 ES6 사용하고 싶다면 Babel과 같은 컴파일러를 사용해야 한다. Babel은 독립 실행형 도구로 실행하거나 빌드 시스템에서 사용할 수 있다. Grunt, Gulp 및 Webpack 용 Babel 플러그인이 있다.
참고로 이 외에도 여러가지 특징이 있으니 관심이 있다면 git.io/es6features를 번역한 ECMAScript 6 Features를 참고하면 된다.
참고
]]>Composer Autoloader는 상대적으로 빠르게 실행되나 PSR-4 및 PSR-0 autoload 규칙이 설정되는 방식으로 인해 클래스 이름을 확인하기 전에 파일 시스템을 검사해야 한다. 이 경우 성능이 떨어지지만 개발 환경에서는 새로운 Class를 추가할 때 autoload 구성을 다시 작성하지 않고도 즉시 사용할 수 있으므로 편리하다. 그러나 production 환경에서는 클래스의 추가가 발생하지 않기 때문에 성능을 위해서 파일 시스템을 검사하지 않고 클래스를 로드하는 것이 좋다.
Composer는 이러한 문제를 개선하기 위해 autoload 최적화 옵션을 제공한다.
autoload 최적화를 위한 클래스 맵을 생성하는 방법은 다음과 같은 몇가지가 존재한다.
composer.json
에 optimize-autoloader
을 추가
install
또는update
시 별도의 옵션을 주지 않아도 자동적으로 최적화를 수행하므로 편리하다.
{
"name": "project",
...
"config": {
"optimize-autoloader": true
}
}
composer install
/ update
시 -o
또는 --optimize-autoloader
옵션을 추가
install
또는update
시 별도의 옵션을 주어야 하지만 필요시에만 최적화를 하려는 경우 유용하다.
$ composer install -o $ composer update --optimize-autoloader
composer dump-autoload
를 -o
또는 --optimize
옵션과 함께 실행패키지들이 이미 설치된 이후에 최적화면 수행하고자 할 경우 사용 가능하다.
$ composer dump-autoload --optimize
클래스 맵 생성은 기본적으로 PSR-4 / PSR-0 규칙을 클래스 맵 규칙으로 변환한다. 클래스 맵이 클래스의 존재와 위치를 보장하므로 Composer는 파일 시스템을 검사하지 않고 빠르게 클래스를 로드할 수 있다.
특히 PHP 5.6 이상에서는 opcache가 활성화되어 있는 경우 클래스 맵이 opcache에도 캐시될 수 있으므로 초기화 시간을 크게 향상된다.
최적화 레벨 1의 경우 특별한 Trade-offs가 없으므로 production 환경에서는 항상 활성화하는 것이 좋다.
단, 클래스 맵에 누락된 클래스를 호출하는 경우 PSR-4 규칙이 대체되어 파일 시스템 검사가 발생할 수 있다. 하지만 모든 클래스를 검사하는 것보다는 당연히 성능이 뛰어나므로 사용하지 않을 이유가 없다.
누락된 클래스에 의한 성능 저하를 해결하기 위해서 두가지의 레벨 2 최적화 옵션이 존재한다. 이 옵션들은 프로젝트에 존재하지 않는 클래스에 대한 class_exists
검사가 많은 경우 사용할 수 있다.
autoload 최적화를 위한 신뢰할 수 있는 클래스 맵을 생성하는 방법은 다음과 같은 몇가지가 존재한다.
composer.json
에 classmap-authoritative
을 추가
install
또는update
시 별도의 옵션을 주지 않아도 자동적으로 최적화를 수행하므로 편리하다.
{
"name": "project",
...
"config": {
"classmap-authoritative": true
}
}
composer install
/ update
시 -a
또는 --classmap-authoritative
옵션을 추가
install
또는update
시 별도의 옵션을 주어야 하지만 필요시에만 최적화를 하려는 경우 유용하다.
$ composer install -a $ composer update --classmap-authoritative
composer dump-autoload
를 -a
또는 --classmap-authoritative
옵션과 함께 실행패키지들이 이미 설치된 이후에 최적화면 수행하고자 할 경우 사용 가능하다.
$ composer dump-autoload --classmap-authoritative
이 옵션이 활성화될 경우 클래스맵에 찾는 클래스가 없더라도 PSR-4 규칙에 따른 파일 시스템 조사를 하지 않도록 하는 것이다. 또한 이 옵션을 사용하면 레벨 1 최적화는 자동적으로 활성화 된다.
이 옵션의 추가 전후의 autoload_real.php
파일 변화를 보면 차이가 명확히 보인다.
public static function getLoader()
{
...
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
...
}
public static function getLoader()
{
...
} else {
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
...
}
이 욥션을 사용하면 PSR-4 규칙을 통한 파일 시스템 검사를하지 않으므로 autoloader가 항상 빠르게 동작한다. 하지만 이로 인해 런타임 상에서 클래스가 생성되는 경우 클래스를 찾을 수 없으므로 "class not found" 오류가 날 수 있으므로 주의해서 사용해야 한다. 특히 개발 환경에서는 클래스의 추가가 자주 일어날 수 있으므로 불편하다(클래스 추가시마다 클래스 맵을 재생성 해야 함).
또한 이 최적화 옵션은 최적화 레벨 2/B와 동시에 사용할 수 없다.
autoload 최적화를 위한 APCu 캐시를 사용하는 방법은 다음과 같은 몇가지가 존재한다.
composer.json
에 apcu-autoloader
을 추가
install
또는update
시 별도의 옵션을 주지 않아도 자동적으로 최적화를 수행하므로 편리하다.
{
"name": "project",
...
"config": {
"apcu-autoloader": true
}
}
composer install
/ update
시 --apcu-autoloader
옵션을 추가
install
또는update
시 별도의 옵션을 주어야 하지만 필요시에만 최적화를 하려는 경우 유용하다.
$ composer install --apcu-autoloader $ composer update --apcu-autoloader
composer dump-autoload
를 --apcu
옵션과 함께 실행패키지들이 이미 설치된 이후에 최적화면 수행하고자 할 경우 사용 가능하다.
$ composer dump-autoload --apcu
이 옵션은 APCu 캐시를 클래스 맵의 fallback으로 추가한다. 발견된 class는 APCu에 캐시되므로 다음 요청시에는 빠르게 반환한다. 최적화 레벨 2/A와 달리 레벨 1 최적화를 자동적으로 활성화하지 않으므로 필요시 수동으로 활성화 해야 한다.
APCu가 사용 가능해야 하는 제약이 있으며 APCu 메모리를 사용해 autoload를 수행하지만 신뢰할 수 있는 클래스 맵에서와 같이 클래스를 찾지 못하는 오류가 발생할 수 있다.
또한 이 최적화 옵션은 최적화 레벨 2/A와 동시에 사용할 수 없다.
위에서 소개한 바와 같이 클래스 맵을 생성하는 최적화 레벨 1은 기본적으로 항상하는 것이 좋다. 최적화 레벨 2의 경우는 성능 상의 이점이 있으나 프로그램의 상황에 따라 오류 발생 가능성이 있으니 production 환경에서 상황에 맞게 선택적으로 사용하는 것이 좋다.
core.precomposeunicode
옵션을 설정해서 한글 파일명 사용시 자소 단위로 분리되는 문제를 해결하는 방법을 소개 했었다.
그런데 맥에서 한글 파일명을 사용할 경우에 다른 문제가 하나더 있다. git status
명령으로 변경 파일을 보면 한글이 \306\301...
와 같이 보이는 문제가 있다. 보이기만 이렇게 보이는 것이면 그냥 참겠지만 commit 하는 등의 과정에서 파일명을 지정하는게 어려워진다.
이 문제를 해결하는 방법은 아래와 같다.
git config --global core.quotepath false
git config --local core.quotepath false
core.quotePath
Commands that output paths (e.g. ls-files, diff), will quote "unusual" characters in the pathname by enclosing the pathname in double-quotes and escaping those characters with backslashes in the same way C escapes control characters (e.g. \t for TAB, \n for LF, \ for backslash) or bytes with values larger than 0x80 (e.g. octal \302\265 for "micro" in UTF-8). If this variable is set to false, bytes higher than 0x80 are not considered "unusual" any more. Double-quotes, backslash and control characters are always escaped regardless of the setting of this variable. A simple space character is not considered "unusual". Many commands can output pathnames completely verbatim using the -z option. The default value is true.
결론을 이야기 하자면 특별한 이유가 있지 않는 이상 한글 파일명은 사용하지 않는 것이 정신 건강에 좋다. 그래서 나는 가급적 사용하지 않으려고 하지만 간혹 어쩔 수 없이 써야하는 경우가 있어 위 방법들을 남겨둔다.
]]>나의 경우는 서버 또는 서비스의 상태를 모니터링 하거나 구글 어날리틱스, 애드센스 등의 현황을 Menu Bar에서 바로 확인하기 위해 사용한다. 하지만 앞서 이야기한 것처럼 활용에 제한이 없으므로 자신이 모니터링하고자하는 모든 정보를 만들어 추가할 수 있다(게다가 간단하다).
BitBar에 표시되는 내용은 플러그인에서 출력한 결과들이므로 원하는 정보를 출력해 줄 플러그인들이 필요하다.
플러그인의 설치는 간단하다. 지정된 플러그인 폴더에 원하는 플러그인 파일을 넣고 메뉴에서 'Refresh' 를 실행하면 된다.
BitBar는 플러그인을 실행해서 결과를 출력해주므로 해당 플러그인들은 실행 권한을 가지고 있어야 한다.
chmod +x plugin.sh
플러그인 폴더의 위치를 확인하거나 변경하려면 Preferences 하위의 메뉴에서 원하는 메뉴를 고르면 된다.
플러그인 실행 결과에 대한 갱신 시간은 플러그인의 파일 이름에 아래의 형식을 사용함으로써 지정 가능하다.
{name}.{time}.{ext}
예를들어 date.1m.sh
플러그인은 1분 간격으로 실행되어 출력된 결과로 갱신된다.
이미 만들어진 플러그인을 사용한 것뿐 아니라 Plugin API를 이용해서 원하는 플러그인을 직접 만들 수도 있다.
---
만으로 구성된 행이 있으면 그 아래의 행은 해당 플러그인의 드롬 다운에 표시되지만 메뉴 표시 줄 자체에는 나타나지 않는다.--
로 시작하는 줄은 하위 메뉴로 나타난다.|
를 이용해서 해당 줄에 여러가지 정보를 포함할 수 있다.
아래는 matryer/bitbar 에 소개된 예시들이다. 예시들을 보면 알겠지만 자신이 익숙한 언어와 BitBar API를 사용해서 간단히 만들 수 있다.
#!/bin/bash
date
#!/bin/bash
# the current date and time
date
# the current username
echo $USER
# the current user id
id -u
#!/bin/bash
echo "One"
echo "Two"
echo "Three"
echo "---"
echo "Four"
echo "Five"
echo "Six"
#!/bin/bash
curl -m 1 http://example.com -I >/dev/null 2>&1
[ $? -gt 0 ] && echo "FAIL | color=red" || echo "OK | color=green"
echo "---"
echo "Show Graphs | color=#123def href=http://example.com/graph?foo=bar"
echo "Show KPI Report | color=purple href=http://example.com/report"
]]>