Follow-up to #2303673: Implement stackphp; cleanup handlePageCache() and preHandle()

Problem/Motivation

Services tagged with http_middleware are not constructed the same way as others. For example the calls parameter is not respected.

In addition, service construction of middlewares fails entirely if something accesses a middleware via container:

$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
$kernel->boot();
$kernel->getContainer()->get('http_middleware.reverse_proxy');

This fails with:

Recoverable fatal error: Argument 1 passed to Drupal\Core\StackMiddleware\ReverseProxyMiddleware::__construct() must implement interface Symfony\Component\HttpKernel\HttpKernelInterface, instance of Drupal\Core\Site\Settings given

Proposed resolution

Either document that clearly or fix the implementation.

Remaining tasks

User interface changes

API changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

znerol’s picture

FileSize
1.96 KB
znerol’s picture

Status: Active » Needs review
znerol’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 1: 2343677-demo.patch, failed testing.

damiankloip’s picture

FileSize
682 bytes

Hmm, yes. I guess we have a couple of options, this goes for injected parameters and setters (as your demo patch shows):

- Remove the actual definitions from the service container after our compiler pass (patch attached for that). These should not be used outside of the stack anyway?
- Allow the httpkernel service to be passed in so we can create an instance but the stack would replace this anyway
- Implement our own builder (would be in a similar problem space though, and compiler pass could do similar)
- Don't use the StackBuilder at all; rely on passing the correct decorated kernel into the next. This could easily break and lead to strange things. A developer could easily add the wrong parameter to one of them and it would break the whole stack. Seems too fragile maybe?
- Just document this somewhere and leave it as that (meh).

Thoughts?

znerol’s picture

I just tested @pounard version from #2303673-103: Implement stackphp; cleanup handlePageCache() and preHandle(), it does not suffer of those problems.

damiankloip’s picture

Yes, I guess this is because it adds the parameter from the last kernel id to the actual definition.

damiankloip’s picture

FileSize
43.49 KB

Here is pounards patch from mentioned issue in #6.

pounard’s picture

Indeed, this fixes the kernel definition problem. If you want to keep the StackBuilder dependency, it's still possible to fix the definitions during the compiler pass the exact same way, it only takes one or two lines of code to add.

As mentioned in the other issue, the only problem by getting rid of the StackBuilder is that we cannot chain terminate() calls, but the fact that the stackphp initiative does only document the KernelInterface and not the TerminableInterface mitigates that.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
1.29 KB

Yes, indeed. We could keep things how they are, let the stack do what it is doing, but fix the definitions in the compiler pass so it solves the problems this issue outlines? Something like this.

znerol’s picture

#10 resolves the $container->get('http_middleware.reverse_proxy'); issue, but it does not have any effect on how middlewares are constructed normally. That means that we still cannot use calls: in the YAML service definition etc.

znerol’s picture

Status: Needs review » Needs work
damiankloip’s picture

ok, well I don't think this is possible with the Stack/Builder class.

dawehner’s picture

So if Stack/Builder doesn't provider what we need, let's just try to use the solution from pounard.
On the longrun moving that generic container feature could be moved upstream somehow.

Crell’s picture

I'm fine with dropping the stack library itself, since even with it we need to do Drupal-specific bridge code anyway. The important part is that stackable kernels work, not that they're added using a specific library to do it.

As for avoiding loading middlewares, they're a good example of a service that should be marked private. That would make $container->get() not work for them, which you should never be doing anyway.

dawehner’s picture

As for avoiding loading middlewares, they're a good example of a service that should be marked private. That would make $container->get() not work for them, which you should never be doing anyway.

Yeah probably, this would be certainly good.

damiankloip’s picture

I think we need to decide here first if we want to support getting these services outside the context of the stack.

pounard’s picture

At first sight, I'd say we don't, except for Drupal specific services that may need configuration in specific use cases.

znerol’s picture

@damiankloip: It does not make any difference. Even if we set the middleware services to private, the Stack\Builder still fails to construct middlewares properly if they have e.g. calls in their service definitions.

Crell’s picture

I cannot think of any reason to modify or reuse a middleware outside of, you know, being a middleware. The same applies for a lot of things; event subscribers, for instance, should all be private because there is no good reason to call $container->get() on them, ever. MOST of what we have in the container should be private, frankly.

damiankloip’s picture

FileSize
43.45 KB

Meh, yes. It has no bearing on that problem. Stack is not container aware in any way, so shall we favour pounards approach? Maybe with the addition of private services?

Here is a reroll of that for now.

pounard’s picture

I think that Crell is right, making those services private makes sense, they are not supposed to be used anywhere outside of the stack itself.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
43.39 KB
1.2 KB

Ok, making them private services. So what we lose is the terminate calls for stacked items that the StackedHttpKernel provides. I guess people could use DestructableInterface if there were some uses? Not sure.

pounard’s picture

Wouldn't those services be set private directly in the yaml file instead ? I sounds odd to leave this responsability to the compiler pass ; While I agree that those services should be set private by the provider, I don't see the point of forcing them to be. For example, if a module set such service public for some various reason (I don't see any good reason right now, but some may rise anytime in the future in specific edge use cases) - why forcing at compilation time while it might be set directly in the definition file instead ?

damiankloip’s picture

ok, I was going with the enforcement route. We can add this just to the definitions though, I don't care either way.

Crell’s picture

I agree, let's just mark them private in the yaml file. No sense being fancy if we don't have a clear need to be.

Damian, I thought we said earlier that we were going to keep the Stack library for the Stacked kernel.

dawehner’s picture

Damian, I thought we said earlier that we were going to keep the Stack library for the Stacked kernel.

The problem is that the container does not allow an array of references, sadly.

damiankloip’s picture

FileSize
43.86 KB
1.43 KB

Marking the services in the YAML definition instead.

znerol’s picture

@dawehner: It seems to me that at least container parameters may actually contain arrays with service references. See ParameterBag::resolveValue().

This would mean that it might be possible to keep the StackedHttpKernel (which also handles terminate()) if we'd just stick the sorted array of middleware services into a container parameter.

Fabianx’s picture

Status: Needs review » Needs work

There is definitely a use case to get data out of the middleware service.

e.g.

A Page Cache Middleware that needs to store some data, but don't want to store it on the request object.

So you can then check e.g

$pageCache = container->get('page_cache_middleware');

if ($pageCache->hasX('whatever')) {
  // do something ...
}

The other option is to put the object or all the data on the request itself, but I don't think that is as clean.

If I understand the last comment correctly, according to znerol it should be possible to change the structure, so we can have the stack and still get back the individual services.

znerol’s picture

Status: Needs work » Needs review
FileSize
4.03 KB

Something like this (without kernel parameters though, I was wrong in that regard). No interdiff because this is a different approach.

damiankloip’s picture

Yes, something like that is what I wanted to see originally if we keep the stack builder library (which is a good idea). If this resolves the dependencies properly this looks good.

znerol’s picture

Ok, but this is PoC quality. It needs to be made more robust, e.g., it should test whether the http_kernel service is really a stacked kernel before altering its definition.

damiankloip’s picture

Yes, this approach looks good is what I meant, I will make some changes now.

damiankloip’s picture

FileSize
8.55 KB
6.45 KB

So here is some test coverage too.

Also added the check for the stack you mentioned znerol.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Reviewed already kinda during the process of damian writing it. +1

Not sure whether we want to have a dedicated test for the calls in order to ensure that the "regression" works in the future.

damiankloip’s picture

I am not sure about testing calls, as now we are always dealing with service definitions. So this would just be like us writing a test that the Symfony tests work, or not? :p

dawehner’s picture

Yes, this ensures that the symfony implementation still works, though we have to have to ensure on a more high level way that things are still working.
I don't want to fight things here.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 35: 2343677-35.patch, failed testing.

damiankloip queued 35: 2343677-35.patch for re-testing.

Fabianx’s picture

I am okay with making the services, public: false - if it cannot be done any other way. There are other ways Stack state can be stored somewhere, so RTBC for me too, just needs to pass tests again.

damiankloip’s picture

Status: Needs work » Needs review

Ok, so the tests are good again. This was an issue with the test bot yesterday.

Fabianx’s picture

I have one concern:

- This means that whenever we add a middleware service we force the user to mark the service public: false, because else it breaks e.g. "console" which does an iteration over all public services.

Is it technically possible / feasible to either:

- a) do the marking private as part of the compiler pass and document somewhere that decorated services are hidden automatically?
- b) Output an error message if we try to decorate a public service?

Thanks!

Fabianx’s picture

Status: Needs review » Needs work

As discussed in IRC:

- The public: false is a larger API change (in a way) and needs to be discussed separately.

Lets remove it.

Even with public: false removed, this successfully unbreaks "container:debug" in console! :)

Great work!

This will be RTBC once those three public: false lines are removed.

The rest makes perfect sense to me and I like this method of service definition much more that we use now.

Fabianx’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
7.94 KB
800 bytes

Uploading a patch to get some commit credit :D (for reviewing).

Only implemented my reviewer feedback, so RTBC now.

As this could cause confusiion:

I did not RTBC my own patch here, I rather did not have a novice or anyone else remove those three lines we agreed in IRC to remove, but did it quickly myself as I had that diff applied to test it locally anyway.

znerol’s picture

dawehner’s picture

Uploading a patch to get some commit credit :D (for reviewing).

Really, we should fix our process ... and our idea that credits count actually, because our model is best upon trust
which you should get when you give good reviews and all other kind of contributions.

+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php
@@ -0,0 +1,116 @@
+/**
+ * @coversDefaultClass \Drupal\Core\DependencyInjection\Compiler\StackedKernelPass
+ */
+class StackedKernelPassTest extends UnitTestCase {

Wait, don't we need a @group in order to appear it in the UI?

damiankloip’s picture

@znerol, I also created #2350491: Discuss/implement our usage of private services but did this on a train journey, and forgot to reference here.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed c033857 and pushed to 8.0.x. Thanks!

diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php
index 654d9a8..5bd6001 100644
--- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php
+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/StackedKernelPassTest.php
@@ -14,6 +14,7 @@
 
 /**
  * @coversDefaultClass \Drupal\Core\DependencyInjection\Compiler\StackedKernelPass
+ * @group DependencyInjection
  */
 class StackedKernelPassTest extends UnitTestCase {
 

Fixed on commit.

  • alexpott committed c033857 on 8.0.x
    Issue #2343677 by damiankloip, znerol, Fabianx: Fixed Stack middleware...

  • alexpott committed 39eb84f on 8.0.x
    Revert "Issue #2343677 by damiankloip, znerol, Fabianx: Fixed Stack...
  • alexpott committed 5379041 on 8.0.x
    Issue #2343677 by damiankloip, znerol, Fabianx, pounard: Fixed Stack...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.