thephpleague / omnipay

A framework agnostic, multi-gateway payment processing library for PHP 5.6+
http://omnipay.thephpleague.com/
MIT License
5.94k stars 928 forks source link

[3.0] PSR-7 and gateway drivers #362

Open judgej opened 8 years ago

judgej commented 8 years ago

I'm trying to understand just how deeply PSR-7 goes (or will go) into the gateway drivers of OmniPay 3.0. Here is my use-case that will help to serve as a point of reference.

I have written a gateway package for a gateway. It will generate PSR-7 Request messages to send to the gateway, will accept PSR-7 Response messages in reply to gateway requests, and will also take ServerRequest messages to parse and handle 3DSecure.

The package is given a factory so that it can create PSR-7 Request messages. It does not otherwise care what the underlying PSR-7 implementation is. It does not get involved in the HTTP client; that is in the application domain and again, it does not care what you use.

Now, how would this plug into OmniPay 3.0? Is there a Request factory I can wire up to the package? Would 3.0 be able to accept the PSR-7 Request messages directly and not have to rip them apart into arrays? Would it be able to give PSR-7 Response and ServerRequest messages directly to the package?

What I'm trying to understand is whether PSR-7 is (or can be) central to the way the core OmniPay and its gateway drivers communicate, or whether PSR-7 is just something that OmniPay uses internally, while still talking to gateways only through its own data structures/setters/getters? Or is there potential for both approaches?

judgej commented 8 years ago

I do realise there are many legacy gateways that need to be supported and we don't have resources to completely rewrite them. But we also need to have an eye on where this all could go in the future.

barryvdh commented 8 years ago

Right now there is the Http\ClientInterface in Omnipay 3.0: https://github.com/thephpleague/omnipay/blob/2245aa7f850ae9e06be9283ace37d6c9ef070d24/src/Common/Http/ClientInterface.php

You can either make a request directly ($client->request($method, $uri, array $headers = [], $body = null)) or send an existing PSR-7 request ($client->sendRequest(RequestInterface $psrRequest)). The actual HTTP client (Guzzle for now), does not care either, as long as it's a valid PSR-7 request. In this case, the Client is also a RequestFactory (which violates single responsibility in favor of laziness). So you can create a PSR-7 request, modify it (follwing the PSR RequestInterface methods) and then send it to the client, without needing to know the specific implementation.

The response for the ClientInterface requests are PSR-7 responses. So you gateway can use that. That directly replaces the the Guzzle3 response object. So you gateway can use the PSR-7 ResponseInterface to get the data from the response (also, without actual implementation knowledge).

So most of the times, you would rewrite the sendData() method in your Omnipay requests.

https://github.com/thephpleague/omnipay-paypal/blob/d3fca215fef074b282ec4e878f2c986cca2e711a/src/Message/AbstractRequest.php#L318-L325

public function sendData($data)
{
    $httpRequest = $this->httpClient->post($this->getEndpoint(), null, http_build_query($data, '', '&'));
    $httpRequest->getCurlOptions()->set(CURLOPT_SSLVERSION, 6); // CURL_SSLVERSION_TLSv1_2 for libcurl < 7.35
    $httpResponse = $httpRequest->send();
    return $this->createResponse($httpResponse->getBody());
}

Could be something like (skipping the Guzzle specific because that is not possible anymore)

public function sendData($data)
{
    $httpRequest = $this->httpClient->createRequest('POST', $this->getEndpoint(), [], http_build_query($data, '', '&'));
    $httpResponse = $$this->httpClient->sendRequest($httpRequest);
    return $this->createResponse((string) $httpResponse->getBody());
}

Or without creating a seperate request

public function sendData($data)
{
    $httpResponse = $this->httpClient->request('POST', $this->getEndpoint(), [], http_build_query($data, '', '&'));
    return $this->createResponse((string) $httpResponse->getBody());
}

So yes, PSR-7 is something that touches the gateways, but depending on the gateway, the effects should be limited.

barryvdh commented 8 years ago

So TLDR:

Moving away from interfaces/implementations tied to Guzzle3, to our own HttpInterface + PSR-7 interfaces:

Guzzle\Http\ClientInterface -> League\Omnipay\Common\Http\ClientInterface

Guzzle\Http\Message\RequestInterface -> Psr\Http\Message\RequestInterface

Guzzle\Http\Message\Response -> Psr\Http\Message\ResponseInterface

judgej commented 8 years ago

Cool - thanks. I'll try writing an OmniPay wrapper for my package and see how it goes, but it looks like it could slip in quite well.

I think my $data here would be a PSR-7 message to give to the httpClient, so the construction of that (putting in the source data, endpoint etc.) would happen before sendData(). Hopefully building this as an exercise will help to understand how it will work.

barryvdh commented 8 years ago

This will be relevant for us also: https://github.com/php-fig/fig-standards/pull/759 When those are accepted, we can split our HttpClient and RequestFactory.

judgej commented 8 years ago

Yes, it is certainly needed for PSR-7 to be really useful as a portable interface. Having to create specific factory wrappers for each implementation now is a pain. I suspect it will always be necessary, but should hopefully be a lot simpler if all the factories use a common set of methods.

TBH I'm surprised the big implementations did not create a separate "HTTP construction and parsing" library that could be shared. That's the difficult bit that would benefit from having one really good implementation with lots of eyes on, rather than dozens of different approaches strapped onto PSR-7 implementations. Maybe there is still room for such a package?

barryvdh commented 8 years ago

TBH I'm surprised the big implementations did not create a separate "HTTP construction and parsing" library that could be shared.

Hmm, isn't what zend-diactoros and guzzlehttp/psr7 are?

judgej commented 8 years ago

Kind of. The PSR-7 implementations do not do simple stuff such as parsing the body. You need to do that yourself, which means inspecting the header and doing json_decode() and str_parse() yourself, and risking missing out on other body formats that may be sent. That is all low-level HTTP stuff that you really don't want to get involved in - you want to ask a message, "give me the data", and simply get the data.

The non-PSR-7 implementations of Guzzle and Diacoros do all that, but the PSR-7 implementations do not. It is a level above HTTP, so that's understandable, but I feel some library to handle that would be good.

Similarly for the application to be able to say to a PSR-7 Request message, "here is a file - just send it and don't bother we with how", and not have to worry about all the intricate complications of headers, encoding, body parts, etc. would be great. Again, that is built into the non-PSR-7 parts of those clients, but not the PSR-7 parts, which is also great, but there is this layer kind of missing. That means it gets reinvented, over and over, in many applications.

barryvdh commented 8 years ago

@judgej Perhaps #368 would make it easier to make/rely on small interfaces, so you can be a bit more flexible with your gateways.

ParadiseStudios commented 6 years ago

v3 is broken. All I am getting is an error: classes/Nyholm\Psr7\Request.php when calling Omnipay::create(); I have php-http/guzzle6-adapter, I don't know what else is required?

barryvdh commented 6 years ago

Are you following the readme and using composer? V3 is working in multiple projects correctly..

ParadiseStudios commented 6 years ago

Edit: It was my autoloader for my smaller classes that was the problem. I had to make an array and only allow the classes by name. No idea what changed in version 3 that broke my autoloader but I was able to fix it. Not ideal but I wish I hadn't had to spend all day figuring it out the hard way.

Yep, v2 was working perfectly, then I upgraded everything over to v3 and instant breakage occurs. I just removed everything and tried fresh...

remove league/omnipay omnipay/braintree omnipay/stripe

require league/omnipay:^3 omnipay/stripe:^3 omnipay/braintree:"3.0.x-dev"

moneyphp/money suggests installing ext-gmp (Calculate without integer limits) moneyphp/money suggests installing florianv/swap (Exchange rates library for PHP) moneyphp/money suggests installing psr/cache-implementation (Used for Currency caching) php-http/discovery suggests installing puli/composer-plugin (Sets up Puli which is recommended for Discovery to work. Check http://docs.php-http.org/en/latest/discovery.html for more details.) php-http/message suggests installing zendframework/zend-diactoros (Used with Diactoros Factories) php-http/message suggests installing slim/slim (Used with Slim Framework PSR-7 implementation)