"Type compatibility" between StripeClient and StripeClientInterface
I've set up the StripeClient instance via the dependency injection auto wiring in Symfony, that way I am specifying my dependency in terms of the interfaces I interact with. However, while running phpstan over my codebase I got the error:
Access to an undefined property Stripe\StripeClientInterface::$charges.
If I swap my type-hint to Stripe\StripeClient instead, everything works just fine because of all the @property annotations in StripeClient.php https://github.com/stripe/stripe-php/blob/04aedea5ad95b9722aa394a41787751b2b478310/lib/StripeClient.php#L8-L56
It would be helpful, in terms of developer ergonomics, to duplicate the @property annotations in the StripClientInterface. And then if a future developer extends the interface he can override the interface annotations with more specific property annotations in his class. And that way it would be easier to comply with phpstan checks.
@mhitza Thanks for reaching out! We'll have a look and see if this is something we can help improve!
Hi, @mhitza, sorry for the extremely delayed response.
The StripeClientInterface is not supposed to represent the entire StripeClient, rather its intent is to abstract an entity that can make requests. This means that adding all StripeClient properties to StripeClientInterface will be incorrect.
Hi @pakrym-stripe - I'm wondering if you have any suggestions about alternative paths here?
It's important that we're able to typehint to the interface so that we can e.g. use mocks during unit tests. But the status quo is that if we do so, static analysis tools like PHPStan fail to see all of the services that are available, and complain about e.g. Access to an undefined property Stripe\StripeClientInterface::$subscriptions.
As it turns out, manually patching in the docblock info to the SDK (as this ticket requested) doesn't even work unless __get($name) is also added into the interface, which I'm sure is undesirable as well.
But either way, I don't feel that developers should have to make a choice between type-safety and testability. So I'd like to know what other avenues we may be able to pursue here. Thanks!
You should be able to mock classes directly without needing an interface:
$mock = Mockery::mock(\Stripe\StripeClient::class);
$terminalMock = Mockery::mock(\Stripe\Service\Terminal\TerminalServiceFactory::class);
$mock->shouldReceive('getService')
->with("terminal")
->once()
->andReturn($terminalMock);
$terminalMock->shouldReceive('getService')
->with("readers")
->once()
->andReturn($reader);
var_dump($mock->terminal->readers);