On Tue, Jul 6, 2021 at 6:47 PM Adam Frederick <cap...@gmail.com> wrote:

> specifically NOT force the idea that content MUST be a string
>>
> What I presented is not advocating forcing the idea that body must be a
> string.  In most cases, it is a string of text, and in all cases, it can be
> a string (although not very useful to have a binary stream turned in to a
> string - but who would do that).  I would imagine most HTTP middleware in
> php is dealing with bodies as strings.  You did make a good point on
> discord, however, that `withBodyString()` would force the response object
> to know how to create a stream from the string.  I don't think this is so
> problematic, however, since it already knows about the stream and the
> stream class,   It would essentially have to do $class =
> get_class($body_stream);  new $class($string); for the withBodyString
> function.  And yes, the middleware could also do this, but that is not
> immediately obvious to middleware makers.
>

That makes a HUGE assumption, however, that the message instance knows
exactly how to construct the stream instance composed into it.

Let me give some examples. In Diactoros, we have several stream types:

- CallbackStream expects a single constructor argument, a callable.
- PhpInputStream expects a single constructor argument, a resource.
- RelativeStream expects two arguments, a StreamInterface to decorate, and
an integer offset
- Our primary Stream implementation has two arguments, a resource or string
representing the resource to create (e.g., "php://memory"), and a
filesystem mode (e.g., 'r', 'a+', 'rw', etc.)

The point is: the message instance cannot know exactly what specific
implementation is composed. This is why we provide
`withBody(StreamInterface $body)` for replacing the body content.


> We deliberately chose not to have any of the PSR-17 interfaces extend from
>> the others. The reason is that there were too many combinations, and most
>> times, you only need 1, 2, or 3 capabilities, making it relatively easy to
>> compose them into your classes via dependency injection. If you find
>> yourself needing multiple capabilities regularly, create your own interface
>> that extends the set of interfaces you use regularly, and type-hint on that
>> in your middleware or handlers.
>>
>
> Right, it can be done.  That's not my issue.  My issue is that it is not
> standardized.  Again, doing it the way you are mentioning relies on the
> non-standard that whatever is using my middleware resolves the interface
> dependency.
> I agree that it make sense to separate the factories as different
> interfaces.  But implementations will combine them because the separation
> is unnecessary (
> https://github.com/Nyholm/psr7/blob/master/src/Factory/Psr17Factory.php)
>

Honestly, this is where I'm looking forward to PHP 8.1 and intersection
types, as they will make these sorts of things easier. Users can then
typehint on `ResponseFactoryInterface&StreamFactoryInterface`, and users
can then pick an implementation that provides such combined
implementations, or create them on the fly:

    return new class($responseFactory, $streamFactory) implements
            ResponseFactoryInterface,
            StreamFactoryInterface
        {
            public function __construct(
                private ResponseFactoryInterface $responseFactory,
                private StreamFactoryInterface $streamFactory
            ) {}

            public function createResponse(int $code = 200, string
$reasonPhrase = '') : ResponseInterface
            {
                return $this->responseFactory->createResponse($code,
$reasonPhrase);
            }

            // ... etc
        };

We actually had a number of discussions about this in the PSR-17 WG, and
even considered adding some combinations to the http-factory-util package,
but there were soooo many variants, and we were concerned people would
typehint on the intersection interfaces in the util package instead of the
ones defined in the spec, which would hamper interop.

It may be time to revisit that, as I think a few of these have come up as
"generally useful":

- ResponseFactoryInterface&StreamFactoryInterface (per the above)
- RequestFactoryInterface&UriFactoryInterface&StreamFactoryInterface
-
ServerRequestFactoryInterface&UriFactoryInterface&StreamFactoryInterface&UploadedFileFactoryInterface
- and one combining all of them (e.g., the one from Nyholm/psr7)

That could happen either as an errata/addendum to PSR-17, or a separate
spec.

>
> 2. Middleware constructor interface dependencies should be resolved,
>>> allowing injection of factories.
>>> Combined, the ServerRequestInterface could just inject itself wherever a
>>> PSR 17 factory was required.  However, #2 puts the demand of interface
>>> dependency injection the the implementer.
>>>
>>
>> Constructors cannot be specified in interfaces. Well, they can, but PHP
>> does not require implementations to honor them.
>>
> Not what I meant by "constructor interface dependencies".  I meant
> __construct(RequestFactoryInterface $request){}, where you then rely on
> injection that resolves the implementation of the interface.
>

What are you suggesting, exactly? That we add an example demonstrating this
(using constructor injection to provide PSR-17 factories within PSR-15
implementations for purposes of creating PSR-7 instances) to the metadoc
for PSR-15? Or documentation detailing how you resolve the interface
typehint to an implementation? Or something else?


> But the fact that PHP won't do it, and it would preclude having custom
>> constructors, means it's simply not an option.
>>
>>
>>>
>>> Without a standard, constructing middleware relies on knowing what the
>>> framework will do.  If the assumption is that middleware will have the
>>> factories injected, this should be stated in the standard as the standard.
>>>
>>
>> There are no assumptions.
>>
>> You can instantiate a response, URI, stream, etc. directly in your
>> middleware. (and thus tie yourself to a specific implementation)
>>
>> Or you could inject factories, and use the factories to create the
>> instances.
>>
>> It's up to you as a developer to define what your class needs to do its
>> work. PSR-7, PSR-15, and PSR-17 are very loosely coupled to allow exactly
>> this.
>>
>
> Right, so there are two non-standardized options.
> 1. use dependency injection and rely on the framework injecting the
> dependencies
>

This is exactly what PSR-17 suggests, though! It exists to allow
typehinting in your constructors against the PSR-17 factories in your
classes so as to prevent directly instantiating a concrete PSR-7
implementation. How the framework does that is really up to the framework.
But this was the intent of PSR-17, and it is spelled out clearly in section
2 of its metadoc.


> 2. create my own set of package requirements for my middleware.  The
> problem with this being the potential for there being 20 PSR 17
> implementations, and various middleware all requiring their own.
>

Your middleware packages should depend on and typehint against PSR-7,
PSR-15, and PSR-17, versus specific implementations. If you typehint
against those, there are certain guarantees provided for you; most salient
to your concerns is the fact that PSR-17's stream factory interface
specifically only addresses resource and file-backed streams, which means
that you can depend on them to (a) be empty, and (b) be writable. As such,
writing your middleware to target the PSR-17 interfaces as constructor
dependencies gives you the interop you're looking for.


> My thoughts on the matter are about allowing the writer of middleware to
> have a standardized mechanism for accessing PSR 17 factories.  If
> dependency injection should be the standard, then standardize it.
>

Again, that is the specific, stated goal of PSR-17, that you inject the
factories in your middleware in order to produce PSR-7 artifacts.


> But, I think an easier mechanism would be to have an
> extended RequestHandlerInterface that implements the factories.  "PSR 21
> ExtendedRequestHandlerInterface implements ...."
>

This violates the separation of concerns principle, and introduces new
questions:

- How does the handler know how to create these artifacts?
- What PSR-7 implementation will the handler use?
- Or is it composing PSR-17 instances to do the work? If so, how does it
get access to those? And if it's composing them... why not just call on
those factories directly instead of proxying to them?


> Think about, as a framework, explaining this to middleware developers.
> You either end up explaining:
> "in your __constructor, make sure to have the various factory parameters
> that you need, and then assign them to properties of your object so you can
> use them in your process method.  We will inject the dependencies as you
> need them."
> Or you tell them
> "you can create a stream by doing $handle->createStream()"
>
> What ends up creating more standardized code?
>

Your second example actually raises more questions for me (see above).
(Some call this style "spooky magic from a distance" as it looks
convenient, but when you dig, you get a lot of questions about how things
actually work, and how you can modify them.)

The first (using DI) is something a framework should cover in its initial
tutorials. In some frameworks, reflection-based DI will resolve them for
you. In others, you will provide configuration detailing which
implementation the DI container should use. Others will have you create and
wire in a factory that will handle this detail (and this is where PSR-11
comes into play, as the factory can then retrieve instances from the
container to resolve dependencies).

The point, however, is that:

- Your middleware is decoupled from a specific framework or PSR-7/PSR-17
implementation. This allows both composition into different frameworks, as
well as substitution for its own dependencies.
- The code is declarative: you know what you need to provide it in order to
create an instance. This makes it testable and self-documenting. Your users
can find the PSR-7/PSR-17 combination that works for them and directly
instantiate the class using concrete implementations.

Am I understanding correctly that you're concerned with the "how do I get
my PSR-17 instances to my middleware" question? Particularly so that your
middleware can operate with multiple frameworks?

If that's the case, it's an open question. The PSR-11 group worked briefly
on a "service providers" spec that would have provided a standard way to
configure services for a PSR-11 container, but the problem discovered is
that DI containers vary widely in how they do their wiring.

Generally, I find that wiring dependencies into a framework tends to be
relatively trivial, which allows me to keep my packages focused on their
specific domain, and dependent only on the FIG specs for their HTTP layer.

-- 
Matthew Weier O'Phinney
mweierophin...@gmail.com
https://mwop.net/
he/him

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/CAJp_myXDj%2BkamG3859cRZOBR%2BBurK%3DaEtuiXNqY9DMFzX5zC9Q%40mail.gmail.com.

Reply via email to