PHP에서도 다른 타입 언어처럼 함수 인자에 타입을 지정할 수 있도록 타입 선언(Type declaration)을 지원한다. 1 동적 타입 언어에서 왜 이런 문법을 사용해야 하는가에 대한 이야기는 여전히 많지만 타입 선언을 사용하는 쪽을 선호한다. TDD를 충실히 한다면 함수에서의 타입 선언이 의미 없다고 생각할 수 있겠지만 여전히 얻을 수 있는 장점도 많기 때문이다. 그 장점 중 하나로 정적 분석을 들 수 있다.
예제
컴파일을 수행하는 언어에서는 이 정적 분석을 통과하지 못하면 컴파일이 되지 않아 실행조차 할 수 없다. 하지만 PHP는 스크립트 언어로 별도의 컴파일 없이 실행할 수 있다. 아래 코드에서는 인터페이스에 선언되지 않은 메소드를 호출하고 있다. 정상적으로 실행이 될까?
<?php
interface FoodInterface
{
}
class FriedChicken implements FoodInterface
{
public function getName()
{
return self::class;
}
}
class Human
{
public function eat(FoodInterface $food)
{
echo $food->getName();
}
}
이제 이 코드를 실행해보자.
<?php
$chicken = new FriedChicken;
$me = new Human;
$me->eat($chicken);
위 코드를 php에서 실행하면 FriedChicken
이 출력되는 것을 볼 수 있다. 즉, FoodInterface
에 getName()
메서드가 선언되어 있지 않더라도 이 메서드를 호출하는 것이 가능하다. 이런 경우라면 getName()
가 없지만 FoodInterface
를 구현한 다른 인스턴스라면 분명 문제가 생긴다. PHP는 여전히 동적 타입 특성을 갖고 있기 때문에 이런 문제를 해결하기 어렵다.
class Human
{
public function eat(FoodInterface $food)
{
// 타입 선언을 했는데도 덕타이핑을 하는 것은 이상함
if (!method_exists($food, 'getName')) {
throw InvalidArgumentException();
}
echo $food->getName();
}
}
여기서는 코드 규모가 작고 간단한 테스트 코드를 작성했기 때문에 쉽게 확인할 수 있었다. 즉, 정적 분석 없이도 테스트를 잘 작성한다면 문제가 없겠지만 제대로 테스트가 작성되어 있지 않거나 코드의 규모가 큰 경우에는 이런 문제를 빠르게 검출하기 어렵다.
이런 상황에서 코드를 실행하지 않고도 문제를 찾기 위해 etsy/phan을 사용할 수 있다.
phan 사용하기
이 패키지는 php-ast 확장을 추가로 요구한다. 맥 또는 리눅스 환경은 리포지터리를 받아 phpize
를 통해 간단히 설치할 수 있고 윈도 환경은 미리 컴파일 된 ast.dll
을 받아 설치하면 된다. php.ini
를 수정하는 것을 잊지 말자.
$ brew install php71
$ git clone https://github.com/nikic/php-ast.git
$ cd php-ast
$ phpize
$ ./configure
$ make install
그리고 사용할 패키지에 phan을 추가한다.
$ composer require --dev etsy/phan
$ vendor/bin/phan --help
phpcs를 사용해본 경험이 있다면 크게 다르지 않게 사용할 수 있다.
$ vendor/bin/phan -l src
src/foodie.php:18 PhanUndeclaredMethod Call to undeclared method \FoodInterface::getName
인터페이스에 정의되지 않은 getName
을 호출했다는 사실을 확인 가능하다.
개발 환경에서의 이런 문제는 제대로 된 IDE(e.g. PHPStorm)를 사용한다면 미리 발견할 수 있다. CI/CD을 하고 있다면 phan을 중간에 추가하는 것도 좋은 아이디어다.