며칠 전 불안정한 테스트를 수정하던 중 공장 내에서 고유 하고 유효한 값이 필요하다는 사실이 밝혀졌습니다. Laravel은 일반적으로 fake()
도우미를 통해 액세스하는 FakerPHP를 래핑합니다. FakerPHP에는 valid()
및 unique()
와 같은 수정자가 포함되어 있지만 한 번에 하나씩만 사용할 수 있으므로 정확히 제가 필요했던 fake()->unique()->valid()
수행할 수 없습니다.
이로 인해 우리 자신만의 수정자를 만들고 싶다면 어떨까 하는 생각이 들었습니다. 예를 들어, uniqueAndValid()
또는 기타 수정자입니다. 프레임워크를 어떻게 확장 할 수 있나요?
나는 내 생각의 기차를 버릴 것이다.
과도하게 엔지니어링된 솔루션에 뛰어들기 전에 항상 더 간단한 옵션이 있는지 확인하고 내가 다루고 있는 내용을 이해하고 싶습니다. 이제 fake()
도우미를 살펴보겠습니다.
function fake($locale = null) { if (app()->bound('config')) { $locale ??= app('config')->get('app.faker_locale'); } $locale ??= 'en_US'; $abstract = \Faker\Generator::class.':'.$locale; if (! app()->bound($abstract)) { app()->singleton($abstract, fn () => \Faker\Factory::create($locale)); } return app()->make($abstract); }
코드를 읽으면 Laravel이 싱글톤을 컨테이너에 바인딩하는 것을 볼 수 있습니다. 그러나 추상을 살펴보면 인터페이스를 구현하지 않는 일반 클래스이며 개체는 팩토리를 통해 생성됩니다. 이것은 상황을 복잡하게 만듭니다. 왜?
인터페이스라면 기본 \Faker\Generator
클래스를 확장하는 새 클래스를 만들고 몇 가지 새로운 기능을 추가한 다음 컨테이너에 다시 바인딩할 수 있기 때문입니다. 하지만 우리에겐 이런 사치가 없습니다.
관련된 공장이 있습니다. 이는 단순한 인스턴스화가 아니라는 것을 의미합니다. 어떤 논리가 실행되고 있습니다. 이 경우 공장에서는 일부 공급자(PhoneNumber, Text, UserAgent 등)를 추가합니다. 따라서 리바인드를 시도하더라도 원래 \Faker\Generator
반환하는 팩토리를 사용해야 합니다.
솔루션 🤔? "1번 항목에 설명된 대로 새 발전기를 반환하는 자체 공장을 만드는 것을 방해하는 것이 무엇입니까?"라고 생각할 수도 있습니다. 글쎄요, 우리는 그렇게 할 수 있지만 그렇게 하지 않을 것입니다! 우리는 여러 가지 이유로 프레임워크를 사용하는데 그 중 하나가 업데이트입니다. FakerPHP가 새로운 공급자를 추가하거나 대규모 업그레이드를 하면 어떻게 되나요? Laravel은 코드를 조정할 것이며, 변경 사항을 적용하지 않은 사람들은 아무 것도 눈치 채지 못할 것입니다. 그러나 우리는 제외될 것이며 코드가 깨질 수도 있습니다(아마도). 그렇습니다. 우리는 그렇게까지 가고 싶지 않습니다.
이제 기본 옵션을 살펴보았으므로 디자인 패턴과 같은 고급 옵션을 생각해 볼 수 있습니다. 정확한 구현이 필요하지 않으며 문제에 익숙한 것이 필요합니다. 이것이 바로 제가 항상 그들을 아는 것이 좋다고 말하는 이유입니다. 이 경우 기존 기능을 유지하면서 새로운 기능을 추가하여 Generator
클래스를 "장식"할 수 있습니다. 좋은 것 같나요? 방법을 보자!
먼저 FakerGenerator
새 클래스를 만들어 보겠습니다.
<?php namespace App\Support; use Closure; use Faker\Generator; use Illuminate\Support\Traits\ForwardsCalls; class FakerGenerator { use ForwardsCalls; public function __construct(private readonly Generator $generator) { } public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator { return new UniqueAndValidGenerator($this->generator, $validator); } public function __call($method, $parameters): mixed { return $this->forwardCallTo($this->generator, $method, $parameters); } }
이것이 우리의 "데코레이터"가 될 것입니다. 이는 기본 Generator
종속성으로 예상하고 새로운 수정자 uniqueAndValid()
를 도입하는 간단한 클래스입니다. 또한 Laravel의 ForwardsCalls
특성을 사용하여 기본 개체에 대한 호출을 프록시할 수 있습니다.
이 특성에는 두 개의 메소드(
forwardCallTo
및forwardDecoratedCallTo
가 있습니다. 데코레이팅된 개체에 메서드를 연결하려면 후자를 사용하세요. 우리의 경우 항상 단일 호출이 발생합니다.
또한 사용자 정의 수정자인 UniqueAndValidGenerator
를 구현해야 하지만 이것이 기사의 요점은 아닙니다. 구현에 관심이 있다면 이 클래스는 기본적으로 FakerPHP와 함께 제공되는 ValidGenerator 와 UniqueGenerator가 혼합된 것입니다. 여기에서 찾을 수 있습니다.
이제 AppServiceProvider
에서 프레임워크를 확장 해 보겠습니다.
<?php namespace App\Providers; use Closure; use Faker\Generator; use App\Support\FakerGenerator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->extend( $this->fakerAbstractName(), fn (Generator $base) => new FakerGenerator($base) ); } private function fakerAbstractName(): string { // This is important, it matches the name bound by the fake() helper return Generator::class . ':' . app('config')->get('app.faker_locale'); } }
extend()
메소드는 주어진 이름과 일치하는 추상이 컨테이너에 바인딩되었는지 확인합니다. 그렇다면 클로저 결과로 해당 값을 재정의합니다. 다음을 살펴보세요.
// Laravel: src/Illuminate/Container/Container.php public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { // You are interested here $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved($abstract)) { $this->rebound($abstract); } } }
그렇기 때문에 우리는 fake()
도우미가 컨테이너에 바인딩하는 것과 동일한 이름을 생성하는 fakerAbstractName()
메서드를 정의했습니다.
혹시 놓치셨다면 위 코드를 다시 확인해보시고 댓글 남겨주세요.
이제 fake()
호출할 때마다 FakerGenerator
인스턴스가 반환되고 우리가 도입한 사용자 정의 수정자에 액세스할 수 있습니다. FakerGenerator
클래스에 존재하지 않는 호출을 호출할 때마다 __call()
트리거되고 이를 forwardCallTo()
사용하여 기본 Generator
로 프록시합니다.
그게 다야! 마침내 fake()->uniqueAndValid()->randomElement()
수행할 수 있게 되었는데 정말 매력적이네요!
결론을 내리기 전에 이것이 순수한 데코레이터 패턴이 아니라는 점을 지적하고 싶습니다. 그러나 패턴은 신성한 텍스트가 아닙니다. 필요에 맞게 조정하고 문제를 해결하십시오.
프레임워크는 매우 유용하며 Laravel에는 많은 기능이 내장되어 있습니다. 그러나 프로젝트의 모든 극단적인 경우를 다룰 수는 없으며 때로는 막다른 골목에 부딪힐 수도 있습니다. 그런 일이 발생하면 언제든지 프레임워크를 확장할 수 있습니다. 우리는 이것이 얼마나 간단한지 살펴보았습니다. 이 Faker 예시를 넘어 적용되는 주요 아이디어를 이해하셨기를 바랍니다.
항상 간단하게 시작하고 문제에 대한 가장 간단한 해결책을 찾으십시오. 필요할 때 복잡성이 발생하므로 기본 상속이 트릭을 수행하는 경우 데코레이터나 다른 것을 구현할 필요가 없습니다. 프레임워크를 확장할 때 손실이 이득보다 더 커지는 너무 멀리 가지 않도록 하십시오. 프레임워크의 일부를 스스로 유지 관리하고 싶지는 않습니다.