Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof-of-Concept: Implementation of an ApplicationPipeline class for better static code analysis #107

Closed
wants to merge 4 commits into from

Conversation

boesing
Copy link
Member

@boesing boesing commented Jan 31, 2022

Q A
BC Break no
New Feature yes

Description

As requested in #105, this provides an ApplicationPipeline implementation which fits directly in the MiddlewarePipeInterface within Application#__construct.

However, in a very strict PoV, this introduces a BC break when applications do directly check for MiddlewarePipe implementation rather than MiddlewarePipeInterface. Not sure if we need to address this as working with concrete implementations is not suggested, but I wanted to drop that information here so we can decide on how to proceed.

I'd be also fine to target 4.0.0 with this.

closes #105

@boesing boesing added the Enhancement New feature or request label Jan 31, 2022
@boesing boesing added this to the 3.10.0 milestone Jan 31, 2022
@boesing boesing force-pushed the feature/application-pipeline branch from 0b470f6 to 276bf12 Compare January 31, 2022 11:56
@boesing boesing removed this from the 3.10.0 milestone Jan 31, 2022
@boesing boesing added the RFC label Jan 31, 2022
@gsteel
Copy link
Member

gsteel commented Jan 31, 2022

I frequently create "Marker Interfaces" for app specific pipelines that are simply interface MyPipeline extends MiddlewarePipeInterface {} and then refer to these in config/factories, but of course you don't actually get an implementation of these interfaces with this approach. My question is, would it be better to make MiddlewarePipe non-final in stratigility and instead make all of its public methods individually final? This way, I can safely extend MiddlewarePipe in an empty class to return an actual implementation of the correct type??

@boesing
Copy link
Member Author

boesing commented Jan 31, 2022

I would prefer having the whole pipeline retrievable via MiddlewarePipeInterface rather than ApplicationPipeline.

So I'd be open to add another service to reference MiddlewarePipeInterface instead.
One thing which we have to keep in mind is that when using laminas-servicemanager, we can not simply switch ApplicationPipeline to be an alias while adding the existing factory for MiddlewarePipeInterface. The reason is, that delegators can only be registered for services and thus existing delegators pointing to the fictional ApplicationPipeline service won't be applied anymore.


In order to these changes, the following needs to be done to this PR:

  1. add alias MiddlewarePipeInterface pointing to ApplicationPipeline service
  2. change ApplicationFactory to retrieve MiddlewarePipeInterface instead of ApplicationPipeline
  3. Directly add @deprecated to the ApplicationPipeline to mark it deprecated so we can remove it in 4.0.0

In v4.0.0 we can drop all ApplicationPipeline references, change the ApplicationPipelineFactory to return the MiddlewarePipe again (as done before this patch) and thats it. Should be a smooth migration path as we would register the MiddlewarePipeInterface as a service instead of an alias and thus, all existing delegators which were registered in upstream projects targeting the fictive ApplicationPipeline service could be retargeted to the MiddlewarePipeInterface instead.

WDYT?

@gsteel
Copy link
Member

gsteel commented Jan 31, 2022

Sorry if I'm adding noise here. I think I misunderstood the ultimate goal of getting rid of ApplicationPipeline - In my previous comment I was referring to re-usable pipelines I'm in the habit of creating which I thought shared a similar mechanism for factory creation and could thus also benefit from better SA and a "best practice" way of writing these… I'll shut up now 😂

@boesing boesing changed the base branch from 3.10.x to 3.11.x April 20, 2022 10:19
@boesing boesing added this to the 3.11.0 milestone Apr 20, 2022
@boesing boesing force-pushed the feature/application-pipeline branch from 276bf12 to f52102f Compare April 20, 2022 10:19
Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, don't see any problems/defects in the patch, but what I'm missing is why this is needed :|

Is this so that downstream consumers can use @var ApplicationPipeline $pipeline, with better type-hinted methods? If we mark it @internal, it doesn't really help then :D

use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class ApplicationPipeline implements MiddlewarePipeInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this @internal? If it's a PoC, that allows us to modify it in future

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document the purpose of this class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can not mark it as @internal as static analysis might report InternalClassUsage when used in a dependency configuration (in delegators).

Actually, the class does not exist (but is being retrieved from the container) and therefore it might be used in upstream projects already.

Copy link
Member Author

@boesing boesing Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about deprecating it to make sure that with 4.0.0, this "service" will be replaced with the MiddlewarePipeInterface.
Hm, but well, deprecation will also trigger psalm issue 😂

*/
public function __clone()
{
$this->pipeline = clone $this->pipeline;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test this cloning

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ocramius
Copy link
Member

Whoops, psalm-baseline.xml needs some rebasing, sorry :(

@geerteltink
Copy link
Contributor

I don't get why this is needed. What is the use case for this?

@boesing
Copy link
Member Author

boesing commented Apr 21, 2022

I don't get why this is needed. What is the use case for this?

The use-case is, that we get rid of magic services which do not represent actual classes but in-fact do provide a service via the container. Using FQCN are way more helpful than using random strings.

Given the fact, that some1 might want to decorate the application pipeline (whyever), one has to create a delegator for that non-FQCN (but namespaced, why the heck is it namespaced when its not an actual class anyways?) service name and then still needs to search in the source code what kind of service is actually returned.


Maybe this makes it clear:

use Mezzio\ApplicationPipeline;

We already use it as FQCN but it is not a class which does exist. Therefore, thats not really helpful when it comes to IDE autocompletion and stuff.


And this:

<UndefinedClass occurrences="3">
<code>ApplicationPipeline</code>
<code>ApplicationPipeline</code>
<code>\Zend\Expressive\ApplicationPipeline</code>
</UndefinedClass>

…es in mezzio projects

This also reduces the baseline a little as we do not have a reference to a non-existing class anymore.

Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
@boesing boesing force-pushed the feature/application-pipeline branch from f52102f to a59a70b Compare April 21, 2022 18:10
This also adds the purpose of the application pipeline implementation.

Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
@boesing boesing requested a review from Ocramius April 21, 2022 18:24
Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
Copy link
Contributor

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a thought: instead of introducing a new class, we could:

  • Map Laminas\Stratigility\MiddlewarePipeInterface to the ApplicationPipelineFactory
  • Alias the existing ApplicationPipeline service to MiddlewarePipeInterface
  • Add migration documentation suggesting that if users want to substitute an alternative application pipeline, they map it to MiddlewarePipeInterface instead of ApplicationPipeline
  • Remove the ApplicationPipeline service in v4.

This eliminates the need for a proxy class such as you've proposed, and accomplishes the same thing with regards to static analysis.

As-is, I'm "meh" for this patch

@boesing
Copy link
Member Author

boesing commented Apr 22, 2022

Changing service names in minors always leads to delegator registration issues.

Project config 3.10.X:

$config = [
    'dependencies' => [
       'delegators' => [
           'Mezzio\ApplicationPipeline' => [
                static function (ContainerInterface $container, string $requestedName, callable $pipelineFactory) {
                       $pipeline = $pipelineFactory();
                       $pipeline->pipe(MyMiddleware::class);
                       return $pipeline;
                } 
           ],
       ],
    ],
];

Project config 3.11.X:

$config = [
    'dependencies' => [
       'delegators' => [
           'Laminas\Stratigility\MiddlewarePipeInterface' => [
                static function (ContainerInterface $container, string $requestedName, callable $pipelineFactory) {
                       $pipeline = $pipelineFactory();
                       $pipeline->pipe(MyMiddleware::class);
                       return $pipeline;
                } 
           ],
       ],
    ],
];

Thus said, this would introduce a massive BC break to applications (from what I can see).
If we want to do that, just let me know and I'll change my PR regarding this.

@weierophinney weierophinney removed this from the 3.11.0 milestone Jun 29, 2022
@boesing boesing closed this Jun 30, 2022
@boesing boesing removed the Enhancement New feature or request label Jun 30, 2022
@boesing boesing added Won't Fix This will not be worked on and removed Enhancement New feature or request RFC labels Jun 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Won't Fix This will not be worked on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create Mezzio\ApplicationPipeline implementation for better static code analysis
5 participants