Zend View behaviour in a Swoole http server

231 Views Asked by At

we are facing a strange behaviour with a Zend Expressive application using Zend View and running in a swoole http server. This behaviour is related with the Singleton pattern.

We have configured Zend Expressive to run in a swoole http server - https://docs.zendframework.com/zend-expressive-swoole/ - and all apis programmed work without problems (swoole is a rocket!)

But we have tried to go to the next step and we have tried to execute a web interface through the swoole http server.

There, it's where we found the strange behaviour. To simplify my question (we use a lot of view helpers), our web interfaces work with Zend View and use the headTitle helper. When you load the web interface the first time everything is working well but when you reload the page you can see that the meta title is duplicated.

Our handler is next

public function handle(ServerRequestInterface $request) : ResponseInterface
{
    $data = [
        "rand" => rand(1000, 9999),
        "time" => date("Y-m-d H:i:s")
    ];
    return new HtmlResponse($this->template->render('app::my-view', $data));
}

Our view is next

<?php $this->headTitle('My Page'); ?>
<b>Rand</b>: <?php echo $rand; ?><br>
<b>Time</b>: <?php echo $time; ?><br>
<hr>

Searching in Google we found in https://framework.zend.com/blog/2018-03-21-expressive-swoole.html that Zend's team had faced a similar problem:

With an async server, however, the same instance will be used on each and every request. Generally, manipulations of PSR-7 message instances will create new instances, as the interfaces they implement are specified as immutable. Unfortunately, due to technical limitations of the PHP language, we were unable to make the body of response messages immutable. This means that if one process writes to that body, then a subsequent process — or even those executing in parallel! — will receive the same changes. This can lead to, in the best case scenario, duplicated content, and, in the worst, provide incorrect content or perform information leaking!

And they had solved that

To combat these situations, we modified the Psr\Http\Message\ResponseInterface service we register with the dependency injection container: it now returns not an instance of the interface, but a factory capable of producing an instance

But inspecting the Zend Framework classes we saw that we had another problem because the solution was implemented in the code and all requests made to apis were correct

in zend-expressive-swoole/src/ConfigProvider.php you can see

ServerRequestInterface::class         => ServerRequestSwooleFactory::class,

Then we change our point of view and inspect the zend view helpers and we found that the view helper HeadTitle extends from a Container\AbstractStandalone

file: zend-view/src/Helper/HeadTitle
class HeadTitle extends Placeholder\Container\AbstractStandalone

Looking in this AbstractStandalone we discovered that in the constructor method a container is loaded through a singleton

file: zend-view/src/Helper/Placeholder\Container\AbstractStandalone
public function getContainer()
{
    if (! $this->container instanceof AbstractContainer) {
        $this->container = new $this->containerClass();
    }
    return $this->container;
}

Here is the problem. With the Zend Expressive Swoole module, every request and every response are independent but the same Singleton container is used again and again.

Have you faced this problem? How did you solve this?

We are thinking about creating a custom helper to delete all view helpers containers but if we do that we must change a lot of view files to add this new and custom helper.

Thanks in advance

1

There are 1 best solutions below

0
On

I asked this question to zend-expressive-swoole library developer team in https://github.com/zendframework/zend-expressive-swoole/issues/34 and they showed me a way to do that:

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $layout  = clone $this->layout;
    $this->phpRenderer->viewModel()->setRoot($layout);
    $this->phpRenderer->headTitle()->deleteContainer();
    $this->phpRenderer->headMeta()->deleteContainer();
    $this->phpRenderer->seo()->reset();
}