在Laravel服务容器中按需更改混凝土类绑定

在Laravel服务容器中按需更改混凝土类绑定,laravel,inversion-of-control,laravel-artisan,Laravel,Inversion Of Control,Laravel Artisan,如何动态更改具体的类绑定 我正在尝试测试使用外部API的artisan命令 class ConsumeApiCommand extends Command { public function __construct(ClientInterface $client) { parent::__construct(); $this->client = $client; } public function handle()

如何动态更改具体的类绑定

我正在尝试测试使用外部API的artisan命令

class ConsumeApiCommand extends Command
{
    public function __construct(ClientInterface $client)
    {
        parent::__construct();

        $this->client = $client;
    }

    public function handle()
    {
        $api_response = $this->client->request('POST', 'http://external.api/resource');

        $response = json_decode($api_response);

        if(isset($response['error'])) {
            $this->error($response['error']);
        } else {
            $this->status($response['status']);
        }
    }
}
目前,;我可以在我的测试中伪造混凝土类

class FakeServiceProvider extends AppServiceProvider
{
    public function register(): void
    {
        $this->app->bind(ClientInterface::class, function () {
            return new class implements ClientInterface {
                public function request($method, $uri, $headers = [], $body = [])
                {
                    return json_encode(['status' => "You've reached us."]);
                }
            };
        });
    }
}
路过

public function test_can_consume_api_if_authenticated()
{
    $this->artisan('consume:api')
         ->expectsOutput("You've reached us.")
         ->assertExitCode(0);
}
失败;返回最初绑定的类响应
您已经联系到我们。

public function test_cant_consume_api_if_not_authenticated()
{
    $this->app->bind(ClientInterface::class, function () {
        return new class implements ClientInterface {
            public function request($method, $uri, $headers = [], $body = [])
            {
                return json_encode(['error' => "Unauthorized."]);
            }
        };
    });

    $this->artisan('consume:api')
         ->expectsOutput("Unauthorized.")
         ->assertExitCode(0);
}

有可能通过这种方式实现欲望行为吗?或者服务容器绑定不能在请求生存期内更改?

始终允许将新的具体类绑定到Laravel中的接口。 我在这里看到的问题(我在过去的类似场景中也遇到过)是,当您
bind()
新的具体类时,artisan命令已经初始化(使用旧绑定)

在测试用例的
setUp()


要测试这是否是可能的解决方案,只需在命令的
\uu construct()
方法中添加
dump()
,并在测试的
设置中添加另一个。如果我是正确的,您应该会看到che命令的一个先出现,然后是另一个。

我只是给那些可能想知道我是如何解决这个问题的人留下一段代码片段

基于,问题中的源代码还可以,但我必须手动操作所需的接口,将其注入命令并将命令添加到服务容器中

我无法动态重写绑定


你说得对,目前我正在评论$app->make(Kernel::class)->bootstrap();在创造应用特性方面。但恐怕这不是实现这一目标的最干净的方法。
use Illuminate\Container\Container;
use Illuminate\Contracts\Console\Kernel;

class ConsumeApiCommandTest extends TestCase
{
    public function test_can_consume_api_if_authenticated()
    {
        $this->artisan('consume:api')
             ->expectsOutput("You've reached us.")
             ->assertExitCode(0);
    }

    public function test_cant_consume_api_if_not_authenticated()
    {
        // Mock client interface.
        $mock = \Mockery::mock(ClientInterface::class);

        // Here you override the methods you want.
        $mock->shouldReceive('request')->once()
             ->andReturn(json_encode(['error' => "Unauthorized."]));

        $command = new ConsumeApiCommand($mock);

        // Re-register artisan command.
        Container::getInstance()->make(Kernel::class)->registerCommand($command);

        $this->artisan('consume:api')
             ->expectsOutput("Unauthorized.")
             ->assertExitCode(0);
    }
}