파사드는 어떻게 동작하는가? free

2019-08-29

캐스트에 Laravel Explained 는 새 영상 시리즈가 시작되었습니다. 사람들이 많이 궁금해하는 주제를 풀어줄 예정이고 하네요. 첫번째 에피소드는 바로 파사드입니다.


스크린샷 2019-08-29 오후 3.44.40.png


아쉽게도 이 시리즈는 유료 가입자만 볼 수 있네요. 시청 후 간략히 정리해봤습니다.


질문 1. \Gate::denies(‘update’, $project) 에서 \Gate는 어디있는 녀석인가?


\로 시작하는 것은 글로벌 네임스페이스를 의미합니다. 하지만 프로젝트의 루트 디렉터리에 있는 것은 아닙니다.
벨은 애플리케이션을 시작할 때 class_alias 함수를 이용해서 config/app.php에 있는 aliases 항목에 정의되어 있는 클래스들의 별칭(alias)를 글로벌 네임스페이스에 등록합니다.


config/app.php


    'aliases' => [

'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
... 중략
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,

\GateIlluminate\Support\Facades\Gate::class 입니다.


질문 2. denies() 는 스태틱 매서드는 어디서 난 것인가?


Illuminate\Support\Facades\Gate를 열어보면 다음과 같이 되어 있습니다.




namespace Illuminate\Support\Facades;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;

/**
* @method static bool has(string $ability)
* @method static \Illuminate\Contracts\Auth\Access\Gate define(string $ability, callable|string $callback)
* @method static \Illuminate\Contracts\Auth\Access\Gate policy(string $class, string $policy)
* @method static \Illuminate\Contracts\Auth\Access\Gate before(callable $callback)
* @method static \Illuminate\Contracts\Auth\Access\Gate after(callable $callback)
* @method static bool allows(string $ability, array|mixed $arguments = [])
* @method static bool denies(string $ability, array|mixed $arguments = [])
* @method static bool check(iterable|string $abilities, array|mixed $arguments = [])
* @method static bool any(iterable|string $abilities, array|mixed $arguments = [])
* @method static \Illuminate\Auth\Access\Response authorize(string $ability, array|mixed $arguments = [])
* @method static mixed raw(string $ability, array|mixed $arguments = [])
* @method static mixed getPolicyFor(object|string $class)
* @method static \Illuminate\Contracts\Auth\Access\Gate forUser(\Illuminate\Contracts\Auth\Authenticatable|mixed $user)
* @method static array abilities()
*
* @see \Illuminate\Contracts\Auth\Access\Gate
*/
class Gate extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return GateContract::class;
}
}

Illuminate\Support\Facades\Gate에도 denies()는 스태틱 매서드는 정의되어 있지 않고, 부모 클래스인 Illuminate\Support\Facade에도 없습니다.
그럼 대체 denies()는 어디에 있는걸까요?


부모 클래스인 Facade에 보면 __callStatic() 스태틱 매서드가 정의되어 있습니다.


public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

return $instance->$method(...$args);
}

__callStatic() 스태틱 매서드는 정의되지 않은 스태틱 매서드가 호출되면 동작하는 매직 매서드입니다.
앞서 살펴보았듯이 \Gate::denies()를 호출하면 Illuminate\Support\Facades\Gate에도 그 부모인 Illuminate\Support\Facade에도 denies() 스태틱 매서드가 없기 때문에 __callStatic()이 실행됩니다.


__callStatic()의 내용을 살펴보면 static::getFacadeRoot()로 인스턴스를 가져온 다음 그 인스턴스에서 원래 실행하려 했던 매서드를 실행합니다.


getFacadeRoot()static::getFacadeAccessor()로 가져온 값을 static::resolveFacadeInstance()에 넘겨 실행한 후 결과를 반환합니다.


    public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}

static::resolveFacadeInstance()는 첫번째 파메터로 넘겨받은 키에 해당하는 인스턴스를 서비스 컨테이너에서 가져옵니다.(코드는 보지 맙시다 ㅎㅎ)


getFacadeAccessor()는 각 파사드 클래스에서 오버이드 합니다. Illuminate\Support\Facades\Gate도 유하게 가지고 있는 매서드가 바로 getFacadeAccessor()였죠.


    protected static function getFacadeAccessor()
{
return GateContract::class;
}

GateContractIlluminate\Contracts\Auth\Access\Gate 이므로 (참고, Illuminate\Support\Facades\Gate 상단에 use Illuminate\Contracts\Auth\Access\Gate as GateContract;로 정의되어 있습니다.)


\Gate::denies()는 결국 Illuminate\Contracts\Auth\Access\Gate 인스턴스의 denies() 매서드를 실행하는 것입니다.


조금 어려우실 수도 있는데, 단순하게 생각하면


파사드 클래스의 getFacadeAccessor()가 반환하는 값에 해당하는 인스턴스를 서비스 컨테이너에서 가져와서 사용한다고 이해하시면 됩니다.


위의 예제에서는 getFacadeAccessor()GateContract::class를 반환했는데, ‘config` 처럼 문자열을 반환하기도 합니다.


질문 3. Illuminate\Contracts\Auth\Access\Gate는 인터페이스인데 어떻게 denies()를 쓸 수 있나?


아시다시피 인터페이스는 매서드를 선언만 할 뿐 구현하진 않죠.


벨은 실행될 때 서비스 프로바이더가 서비스 컨테이너에 바인딩하는 과정을 거칩니다. Gate 파사드의 경우 Illuminate/Auth/AuthServiceProvider가 그 역할을 하는데요,


protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
});
}

registerAccessGate() 매서드에서 Illuminate\Contracts\Auth\Access\Gate를 싱글톤으로 Illuminate\Auth\Access\Gate에 바인딩합니다.


그래서 앞서 언급한 static::resolveFacadeInstance()에서 Illuminate\Contracts\Auth\Access\Gate를 요청하면 서비스 컨테이너가 Illuminate\Auth\Access\Gate를 되돌려준 것입니다.


마치며


사실 저도 이전에 파사드가 도대체 어떻게 동작되는지 궁금해서 한 번 파을 열어봤었어요. 그런데 파사드 클래스 자체가 너무 간단하게만 작성되어 있어서 바로 길을 잃고 당황스러워했던 기억이 납니다. 그 후로 더이상 궁금증을 갖지 않고 ㅎㅎ 그냥 쓰고만 있었는데요, 오늘 이 영상을 보니 확실하게 이해가 되네요.(글로 정리하느 여러번 봐서 더 그런것 같아요 ㅎㅎ) 제가 정리한 것도 좀 어려우실 수 있긴한데, 슥~ 읽고 말기 보단 IDE로 벨 애플리케이션을 열어두고 설명에 따을 하나하나 따가다보면 더 쉽게 이해가 되실 것 같아요. 혹시 이해가 안되거나 궁금하신 점이 있으면 회신주세요~


1 1 벨 43호
2019년 8월 29



이현석

메쉬 코리아 개발자. 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 2020년에 출간될 Laravel Up & Running 2nd Edition을 번역하고 있습니다.