PSR-7: reuseful Request, how change body content?

100 views
Skip to first unread message

Anton Fedonjuk

unread,
Oct 10, 2019, 7:42:42 PM10/10/19
to PHP Framework Interoperability Group
Class contains method setRequest(RequestInterface $request), his abstact code:
Request body already sets: $request->getBody()->write('...');
I want send this request with other body content, but dont know how:
1. StreamInterface don't have methods as PHP ftruncate()
2. Constructor not defined in StreamInterface: can't use $request->withBody() because dont know how create clone of that Steam object without contents.

Woody Gilk

unread,
Oct 10, 2019, 8:05:42 PM10/10/19
to php...@googlegroups.com
Create a new stream using PSR-17 StreamFactory. 

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/84a56774-1dbf-4ec3-b906-b29e8b322add%40googlegroups.com.
--

Anton Fedonjuk

unread,
Oct 10, 2019, 8:23:48 PM10/10/19
to PHP Framework Interoperability Group
Nope, u dont know factory class name

пятница, 11 октября 2019 г., 3:05:42 UTC+3 пользователь Woody Gilk написал:

Woody Gilk

unread,
Oct 10, 2019, 8:29:23 PM10/10/19
to PHP Framework Interoperability Group
Not sure what you mean, StreamFactoryInterface is designed to create new streams from strings, files, or existing resources. As the editor of PSR-17, I am quite sure this is the case.
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

Anton Fedonjuk

unread,
Oct 10, 2019, 8:35:43 PM10/10/19
to PHP Framework Interoperability Group
Also PSR-7 not "include" PSR-17 - maybe that stream created without factory, we dont know.


пятница, 11 октября 2019 г., 3:05:42 UTC+3 пользователь Woody Gilk написал:
Create a new stream using PSR-17 StreamFactory. 

On Thu, Oct 10, 2019 at 6:42 PM Anton Fedonjuk <antonf...@gmail.com> wrote:
Class contains method setRequest(RequestInterface $request), his abstact code:
Request body already sets: $request->getBody()->write('...');
I want send this request with other body content, but dont know how:
1. StreamInterface don't have methods as PHP ftruncate()
2. Constructor not defined in StreamInterface: can't use $request->withBody() because dont know how create clone of that Steam object without contents.


--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php...@googlegroups.com.

Anton Fedonjuk

unread,
Oct 10, 2019, 8:39:06 PM10/10/19
to PHP Framework Interoperability Group
You know only: $request instanceof RequestInterface, you dont know other details and can use only methods/classes defined in RequestInterface.

пятница, 11 октября 2019 г., 3:29:23 UTC+3 пользователь Woody Gilk написал:
Not sure what you mean, StreamFactoryInterface is designed to create new streams from strings, files, or existing resources. As the editor of PSR-17, I am quite sure this is the case.


On Thu, Oct 10, 2019 at 7:24 PM Anton Fedonjuk <antonf...@gmail.com> wrote:
Nope, u dont know factory class name

пятница, 11 октября 2019 г., 3:05:42 UTC+3 пользователь Woody Gilk написал:
Create a new stream using PSR-17 StreamFactory.

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php...@googlegroups.com.

Matthew Weier O'Phinney

unread,
Oct 11, 2019, 7:42:32 PM10/11/19
to php...@googlegroups.com
To provide new contents, you provide a new body to the request, which means using a new StreamInterface instance. 

To get a new StreamInterface instance, you either instantiate I've directly, per the PSR-7 *implementation* you have installed, or you use a PSR-17 StreamFactoryInterface instance to create it. This is something you would compose into your class as a constructor dependency. 

In each case, you need an *implementation* of one or both of the specs installed, which is already likely the case. 

In the first case, if using Diactoros, your code might look like this:

    $request = $this->getRequest()
        ->withBody(new \Zend\Diactoros\Stream('php://temp', 'wb+'));

In the second, it would look like this:

    $request = $this->getRequest()
        ->withBody($this->streamFactory->createStream());

From there, you can call on the stream's write() method. Alternately, if using the factory, create the entire stream *with contents* to save yourself the step. 

Regarding your point that there is no StreamInterface constructor,
the constructor is not defined in any of the interfaces because:

- We did not want to restrict how implementations wrote constructors.
- PHP ALWAYS allows classes implementing interfaces to override the signature of a constructor. 

As such, you always have to look at how the *implementation* to determine the signature of constructors. 

This is one large reason PSR-17 exists - to standardize how instances of psr-7 interfaces are created. 

Regardless, each of the above examples give you an instance with an empty body. 


--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/84a56774-1dbf-4ec3-b906-b29e8b322add%40googlegroups.com.

Anton Fedonjuk

unread,
Oct 12, 2019, 9:01:36 AM10/12/19
to PHP Framework Interoperability Group
These  are not copies of the current StreamtInterface implementation returned $this->request->getBody()

I am trying to create a class/method that works with any third-party RequestInterface implementation.

I do not know the architecture of StreamtInterface implementation used in the RequestInterface implementation.

class Stream implements StreamtInterface
{
   
protected $handler;
   
   
public function __construct()
   
{
        $this
->handler = tmpfile();
   
}
   
   
public function write($str)
   
{
        fwrite
($this->handler, $str);
   
}
}

class FilteredStream extends Stream
{
   
protected $filter;
   
   
public function __construct(callable $filter)
   
{
        parent
::__construct();
        $this
->filter = $filter;
   
}
   
   
public function write($str)
   
{
        parent
::write(($this->filter)($str));
   
}
}

$request
->withBody(new Stream());
$myAbstractObject
->setRequest($request);
// in other case
$request
->withBody(new FilteredStream('myFilterFunct'));
$myAbstractObject
->setRequest($request);



I think need add "truncate" or/and "withContents" methods to the StreamInterface:

interface StreamInterface
{
   
/**
    * Truncates a stream data to a given length.
    *
    * @see https://www.php.net/manual/en/function.ftruncate.php
    *
    * @param int $size The size to truncate to.
    * @throws \InvalidArgumentException Size not integer or less zero.
    * @throws \RuntimeException Not seekable.
    */

   
public function truncate($size);

   
/**
     * Returns new instance with the specified contents.
     *
     * @param string $str New contents.
     * @return static New instance.
     * @throws \RuntimeException On failure.
     */

   
public function withContents($str);
}

class Stream implements StreamInterface
{
   
public function truncate($size)
   
{
       
if (intval($size) != $size || $size < 0) {
           
throw new \InvalidArgumentException('Size must be not negative integer');
       
}
       
if (! $this->isSeekable()) {
           
throw new \RuntimeException('Stream not seekable');
       
}
        ftruncate
($this->handle, $size);
       
if ($this->tell() > $size) {
           
// Fix cursor position
            $this
->seek(0, SEEK_END);
       
}
   
}
   
   
public function withContents($str)
   
{
        $clone
= clone $this;
        $clone
->rewind();
        $clone
->write($str);
        ftruncate
($this->handle, strlen($str));
       
// or $this->truncate(strlen($str));
       
return $clone;
   
}
}



суббота, 12 октября 2019 г., 2:42:32 UTC+3 пользователь Matthew Weier O'Phinney написал:

Martijn van der Ven

unread,
Oct 12, 2019, 9:23:32 AM10/12/19
to PHP Framework Interoperability Group
To make something that is compatible with unknown third-party implementations of PSR, I would do something like this:

class MySpecialRequestHandler
{
   
private $streamFactory;
   
public function __construct(StreamFactoryInterface $streamFactory)
   
{
        $this
->streamFactory = $streamFactory;
   
}
   
public function handleRequest(RequestInterface $request): RequestInterface
   
{
        $newBody
= $this->streamFactory->createStream('New body!');
       
return $request->withBody($newBody);
   
}
}


This way the user’s dependency injection can take care of providing the correct StreamFactory implementation. Alternatively you could use something like middlewares/utils which includes a Factory class that tries to find the currently available implementation without the need for injection by the user.

There are a lot of reasons why you would want to switch out the body completely. You can for example not guarantee that a truncate() or withContents() method will work on every Stream implementation. How will you hande read-only Streams? Are unseekable Streams a problem? (There are also numerous discussions about cursor placement that may or may not make things hard, just search through the mailing list.) Instead of trying to change the contents of a Stream, it is much better to initiate a new Stream and use that through the withBody() method. This is especially true if you do not know the underlying Stream architecture!

Also note that you can use any StreamInterface implementation. It does not have to be the same implementation as the third-party code is using. If you cannot get access to a StreamFactoryInterface implementation and you cannot detect the constructor of the StreamInterface implementation, you can always chose to supply your own StreamInterface implementation instead. PSR-7 allows full mix-and-matching of implementations.

Anton Fedonjuk

unread,
Oct 12, 2019, 9:48:34 AM10/12/19
to PHP Framework Interoperability Group
if stream not writable then we can replace it to any writeble stream.

суббота, 12 октября 2019 г., 16:23:32 UTC+3 пользователь Martijn van der Ven написал:

Anton Fedonjuk

unread,
Oct 12, 2019, 9:52:42 AM10/12/19
to PHP Framework Interoperability Group
You cant do this. How u create new FiltredStream?


суббота, 12 октября 2019 г., 16:23:32 UTC+3 пользователь Martijn van der Ven написал:
it is much better to initiate a new Stream and use that through the withBody() method. This is especially true if you do not know the underlying Stream architecture!

Anton Fedonjuk

unread,
Oct 12, 2019, 10:02:41 AM10/12/19
to PHP Framework Interoperability Group
Other StreamInterface implementation not guarantee similar contents - compatible != identical.

суббота, 12 октября 2019 г., 16:23:32 UTC+3 пользователь Martijn van der Ven написал:
Also note that you can use any StreamInterface implementation.
Message has been deleted

Anton Fedonjuk

unread,
Oct 12, 2019, 10:39:15 AM10/12/19
to PHP Framework Interoperability Group
Not reusable/resettable objects it`s potential problem in future.
Yes, we can send  factories or add empty instances, but it's harder than add method to reset content.

My current code:
abstract class HttpGateway extends Gateway
{
   
protected $client;
   
protected $request;
   
protected $dataSeparator;

   
public function __construct(ClientInterface $client, RequestFactoryInterface $factory)
   
{
        $this
->client = $client;
       
// Checks properties because extended class can overwrite them.
       
if (! $this->request) {
            $this
->request = $factory->createRequest('GET', $this->getUrl());
       
}
       
if (! $this->dataSeparator) {
            $this
->dataSeparator = ini_get('arg_separator.output') ?: '&';
       
}
   
}
   
   
protected function sendRequest(array $data)
   
{
        $data
= http_build_query($data, '', $this->dataSeparator, PHP_QUERY_RFC3986);
       
if ($request->getMethod() == 'POST') {
            $request
= clone $this->request;
            $request
->getBody()->write($data);
       
} else {
            $uri
= $this->request->getUri()->withQuery($data);
            $request
= $this->request->withUri($uri, true);
       
}
        $response
= $this->client->sendRequest($request);
       
return $this->parseResponse($response);
   
}
}

As result: I can`t use PSR-7 without PSR-17, than why not merge it and adds exception iterfaces?

Matthew Weier O'Phinney

unread,
Oct 14, 2019, 6:32:46 PM10/14/19
to php...@googlegroups.com
On Sat, Oct 12, 2019 at 9:25 AM Anton Fedonjuk <antonf...@gmail.com> wrote:
Not reusable/resettable objects it`s potential problem in future.
Yes, we can send  factories or add empty instances, but it's harder than add method to reset content.


Resetting content means _resetting the StreamInterface instance used with the message_.

This is by design. Since the `withBody()` method returns a new instance, it ensures that changes to the original body stream do not propagate to the current instance. If you want to reset content for the purpose of writing to it, _it should not matter what kind of stream was used before_.

If it does, then your code is tied to an _implementation_, and, if that's the case, you should directly instantiate objects from that implementation, using the signature of their constructors.

If it doesn't (and it _shouldn't_), using a composed PSR-17 StreamFactoryInterface will get you a new stream that you can use immediately, and you're now no longer tied to a specific implementation.
 
My current code:

abstract class HttpGateway extends Gateway
{
   
protected $client;
   
protected $request;
   
protected $dataSeparator;

   
public function __construct(ClientInterface $client, RequestFactoryInterface $factory)
   
{
        $this
->client = $client;
       
// Checks properties because extended class can overwrite them.
       
if (! $this->request) {
            $this
->request = $factory->createRequest('GET', $this->getUrl());
       
}
       
if (! $this->dataSeparator) {
            $this
->dataSeparator = ini_get('arg_separator.output') ?: '&';
       
}
   
}
   
   
protected function sendRequest(array $data)
   
{
        $data
= http_build_query($data, '', $this->dataSeparator, PHP_QUERY_RFC3986);

       
if ($this->request->getMethod() == 'POST') {

            $request
= clone $this->request;
            $request
->getBody()->write($data);
       
} else {

            $uri
= $request->getUri()->withQuery($data);

            $request
= $this->request->withUri($uri, true);
       
}
        $response
= $this->client->sendRequest($request);
       
return $this->parseResponse($response);
   
}
}

As result: I can`t use PSR-7 without PSR-17, than why not merge it and adds exception iterfaces?

You can use PSR-7 without PSR-17 just fine. We did it in Expressive by providing prototype factories, which users could override by registering their own versions that would return instances from the PSR-7 implementation they chose.

PSR-17 _simplifies_ this by providing a standard.

But you can totally use PSR-7 without PSR-17. As an example, a _library_ that consumes PSR-7 instances directly _does not care about the factories at all_, only the PSR-7 instances.

There's reasons to keep them separate.

At the application level, yes, you will generally use them in tandem. But, again, it's not required.


--
he/him

Anton Fedonjuk

unread,
Oct 14, 2019, 6:46:14 PM10/14/19
to PHP Framework Interoperability Group
At first, I not last in chain - my package used in other packages and I they can add filters or something like this to his StreamInterface implementations. As a result, difference in writed content. See up - I already add Stream examles for display this.

вторник, 15 октября 2019 г., 1:32:46 UTC+3 пользователь Matthew Weier O'Phinney написал:

Anton Fedonjuk

unread,
Oct 14, 2019, 7:00:42 PM10/14/19
to PHP Framework Interoperability Group
If you replace psr-17 to same your interfaces/classes then it's not "just fine" for me. In your case, external packages included my package cant use any PSR-7 package with my package.

вторник, 15 октября 2019 г., 1:32:46 UTC+3 пользователь Matthew Weier O'Phinney написал:
You can use PSR-7 without PSR-17 just fine. We did it in Expressive by providing prototype factories, which users could override by registering their own versions that would return instances from the PSR-7 implementation they chose.

Anton Fedonjuk

unread,
Oct 14, 2019, 7:17:17 PM10/14/19
to PHP Framework Interoperability Group
There are many contradictions in your comment. If you can replace/reset my FiltredStream from other example to new in any RequestInterface implementation without PSR-17, just show how do it - code please.


вторник, 15 октября 2019 г., 1:32:46 UTC+3 пользователь Matthew Weier O'Phinney написал:
You can use PSR-7 without PSR-17 just fine.

Alexandru Pătrănescu

unread,
Oct 15, 2019, 3:39:58 PM10/15/19
to php...@googlegroups.com
Hi Anton,
 
Not reusable/resettable objects it`s potential problem in future.
Yes, we can send  factories or add empty instances, but it's harder than add method to reset content.

The pattern that PSR-7 promote is the use of immutable objects.
It is maybe a bit harder to use, in some peoples opinion, but on the long run it reduces the number of bugs in a software.
Maintaining (unnecessary) state is a source of increased maintenance cost (bugs or harder to change code).
Performance wise, there is very small insesizabile impact (when cloning) so don't worry about that.

In terms of creating requests, responses or streams, whoever wants to use it, they can just instantiate it if they depend on the implementation.
If they however wants to not depend on a specific implementation, they can just use PSR-17 interfaces and bind them at DI config.

Also, I want to mention that the code you give as an example has a lot to improve, if you would be willing to accept fast review:
- stop using inheritance between classes. Prefer composition over inheritance: https://en.wikipedia.org/wiki/Composition_over_inheritance
- stop maintaining unnecessary state, just pass the things you want to keep as a state to methods. I suggest you try learning some functional programming like erlang or haskell
- stop using protected keyword, only private. This comes together with not using inheritance.
- don't clone the request as body would be the same object. Just write to the body.
- use identity operator instead of equal operator

Regards,
Alex Patranescu
 

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

Anton Fedonjuk

unread,
Oct 16, 2019, 4:36:04 AM10/16/19
to PHP Framework Interoperability Group
Immutable objects is not problem. I never talked that. But I see hard dependencies between PSR-7 and PSR-17 in real applications - you cant create eggs without chicken...

вторник, 15 октября 2019 г., 22:39:58 UTC+3 пользователь Alexandru Pătrănescu написал:

Anton Fedonjuk

unread,
Oct 16, 2019, 4:38:59 AM10/16/19
to PHP Framework Interoperability Group
See my previous comment to Matthew Weier O'Phinney, can u do this? If u cant this disscussion is mistification.


вторник, 15 октября 2019 г., 22:39:58 UTC+3 пользователь Alexandru Pătrănescu написал:

Alessandro Lai

unread,
Oct 23, 2019, 6:27:34 AM10/23/19
to PHP Framework Interoperability Group
Anton please keep it to a civil tone. Talking about "mistification" is not friendly nor acceptable here.

As already said by MWOP, supporting PSR-7 alone has absolutely sense, and there are tons of packages that do that, because they care only about working on requests, not creating them.

Anton Fedonjuk

unread,
Oct 24, 2019, 9:14:25 AM10/24/19
to PHP Framework Interoperability Group
1. English is not my first language, so what I said may seem offensive, although I did not want this.
2. If there are rules, then no one bothered to acquaint me with them
3. When has openness/truthfulness become not civilized and unacceptable? I think dialogue must be simple and clear, without evasions and omissions.
4. This topic is asked a question and his description/terms. I seems "unfriendly" to ignore it.

среда, 23 октября 2019 г., 13:27:34 UTC+3 пользователь Alessandro Lai написал:
Anton please keep it to a civil tone. Talking about "mistification" is not friendly nor acceptable here.

среда, 23 октября 2019 г., 13:27:34 UTC+3 пользователь Alessandro Lai написал:
supporting PSR-7 alone has absolutely sense, and there are tons of packages that do that, because they care only about working on requests, not creating them.
 
1. "Tons of packages" use only requests or responses - why not separate PSR-7 more?? Use != create.
2. You try support a friend instead of answering topic question. Can u do this? If u cant this disscussion is mistification.

Offtop:
some PSRs define exception interfaces, PSR-7 declares SPL exceptions - this add troubles: exceptions of most packages inherits of \Exception (good practise), they defines special methods.
PSR-7 exceptions have 2 way:
copy special methods to every extension - bad practice
extract special methods in class/trait helper and create unnecessary dependencies - too not good.
Reply all
Reply to author
Forward
0 new messages