Multiple Zend Expressive "applications" on the same server

Hello everyone,

I’m developing several applications and they are all using Zend Expressive.
I want to host those applications on the same server.

So far, I used dedicated routes for each application. For instance, all routes of “My Application 1” start with /app/my-application-1 and all routes of “My Application 2” start with /app/my-application-2, …

But I guess there is a smarter way to do something like this so my application doesn’t have to be aware that there are other Zend Expressive applications next to it.

Any suggestions, advice, or leads about this ?
Thanks a lot :slight_smile:

I’m not entirely sure what you are asking here. But I think this comes down to configuring your webserver (nginx/apache).

Since your applications are all independent, you create separate directories for each application. And then you point your webserver to the public path of each application and link it to the desired url.

Server paths:
/var/www/app-1
/var/www/app-2

Link url to public paths for each application. This can be one of these:

Unless you mean all applications share the same code and only the url is different. I haven’t tried that with expressive yet. But you could think about loading config and routes based on the domain name.

Indeed, I use this in my Apache configuration :

Alias "/app/application-1" "/var/www/my-application-1/public"
<Directory "/var/www/dev/my-application-1/public">
    AllowOverride FileInfo
</Directory>

Alias "/app/application-2" "/var/www/my-application-2/public"
<Directory "/var/www/dev/my-application-2/public">
    AllowOverride FileInfo
</Directory>

That works just fine but all my routes for “Application 1” have to start with /app/application-1 otherwise the route is not “recognize” (unless I missed something of course) :

$app->get('/app/application-1[/]', App\Handler\HomePageHandler::class, 'home');
$app->get('/app/application-1/download/{path:.+}', App\Handler\DownloadHandler::class, 'download');

I would like to know if it’s possible to keep the routes without the /app/application-1 in it but still be able to have example.com/app/application-1 and example.com/app/application-2.

You can with piping: https://docs.zendframework.com/zend-expressive/v3/features/router/piping/#piping
I haven’t used this myself yet so I can’t help you with it much. The problem with piping is that it only match literal paths, so you would need to add your own router inside that middleware. But you can easily move your app-1 module to another path without the need to modify the routes.

For example:

$app->pipe('/api', $bookMiddleware);

In this case you can have route /book/edit. When piping, the path is stripped from the url available in the book middleware. In this case the router would match only against /book/edit and not /api/book/edit. Doing this, you can move your middleware by changing the path from /api to /api/v2 without needing to rewrite your routes in that middleware. I hope that makes sense somehow.

I might be wrong, but I think piping would require a sharing your container between the applications. That could be tricky.

If you’re happy with the aliasing the apps in your Apache config you could pipe a middleware that strips the leading path from the request url. You’d also want to set the baseUrl on the UrlHelper so links come out right. Something like:

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResoonseInterface
{
    $this->urlHelper->setBasePath($this->basePath);
    $uri = $request->getUri();
    $newPath = preg_replace(‘|^’ . $this->basePath . ‘|’, ‘’, $uri->getPath());
    return $handler->handle($request->withUri($uri->withPath($newPath)));
}

Ugh, hard to code on a phone :grimacing: You’ll want to add the middleware to your pipeline before routing.

Thanks @xtreamwayz , it does make sense.
I found this cookbook that explains a bit more what you meant (I think).
I’ll have a look but I think @kynx is right, I’ll have to share a container between my applications.

Thanks @kynx ! I don’t get immediately what you code does but I’ll try and find out !

Just in case anyone ends up here with the same problem, to solve this you need to be working with a “base path”, there is a cookbook that gives hints towards this here: https://github.com/zendframework/zend-expressive/blob/master/docs/book/v2/cookbook/using-a-base-path.md

1 Like

Included in the docs: https://docs.zendframework.com/zend-expressive/v3/cookbook/using-a-base-path/ :smiley:

1 Like

That’s awesome !
mtymek/blast-base-url seems to be exactly what I am looking for !

I still have some issue with using it in Twig though.

It provides the following as example, but I guess I’ll have to create a new Twig Extension based on BasePathViewHelper.php to make it work.

<link rel="stylesheet" href="<?= $this->basePath('/css/style.css') ?>" />

https://docs.zendframework.com/zend-expressive/v3/features/template/twig/#included-extensions-and-functions

1 Like

Yes, you’re right !
I knew this and even though I first thought {{ path('home') }} didn’t take the base path into account, it does !
I have an issue to serve my static files … Trying to find out why ! :slight_smile:

Okay, I figured it out !
First of all, thanks a lot everyone for your help ! :tada:

Here are what I had to do to make mtymek/blast-base-url work with my application :

  1. Despite the fact that the documentation says to add RewriteRule (.*) ./public/$1 in your .htaccess file, it’s not necessary ! Keep the default .htaccess file provided by zendframework/zend-expressive-skeleton ;

  2. Since I use Twig, I use {{ asset('path/to/asset/name.ext') }} to link my assets. It’s very important (and quite obvious … stupid me) to NOT start the path to the asset by a slash / ;

  3. If you use a RedirectResponse() in your Handler, I had to concatenate the base path to the generated URI (from the router) :

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $basePath = $request->getAttribute(\Blast\BaseUrl\BaseUrlMiddleware::BASE_PATH);
        
        ...
    
        return new RedirectResponse($basePath . $this->router->generateUri('home'));
    }
    

With all of this, I just have to configure Apache like this and my application now works whatever the directory I put it in ! Nothing to change in the application itself, just the Apache configuration :partying_face:

Alias "/app/my-app" "/var/www/my-app/public"
<Directory "/var/www/my-app/public">
    AllowOverride FileInfo
</Directory>

EDIT :

I spoke a bit too early …
I still have an issue with {{ asset('path/to/asset/name.ext') }} when I’m not in the “root” directory http://localhost/app/my-app/ (like http://localhost/app/my-app/articles for instance).

<link rel="stylesheet" href="{{ asset('css/style.css') }}">
<link rel="stylesheet" href="{{ absolute_url(asset('css/style.css')) }}">

generates

<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="http://localhost/app/my-app/articles/css/style.css">

instead of

<link rel="stylesheet" href="http://localhost/app/my-app/css/style.css">

Quick fix :

According to documentation we can specify Twig’s assets_url in the configuration.
So I used 'assets_url' => '/app/my-app/' and it works but I guess there is a smarter way (like defining the assets_url using the base path from the BaseUrlMiddleware) !
I’ll keep searching ! :slight_smile: