Last Updated: February 25, 2016
·
2.945K
· tschuermans

Silex - Forward requests

Let's start with a little introduction.
I currently work at a company which uses an API to serve data from multiple services.
This API is built using silex, which is pretty awesome.

We have lots of API endpoints like:
/blog/blog_id/comments/

/blog is mapped to a blog controller using:

$application = new \Silex\Application();
$application->mount('/blog', new \App\Controller\Blog

The blog controller used to look something like this:

namespace App\Controller;

use \Silex\Application,
    \Silex\ControllerProviderInterface;

class Blog implements ControllerProviderInterface
{
    public function connect(Application $application)
    {
        $this->app   = $application;
        $controllers = $this->app['controllers_factory'];

        $controllers->get(
            '/', 
            array($this, 'getAllBlogPosts')
        );
        $controllers->get(
            '/{blogpost_id}/', 
            array($this, 'getOneBlogPost')
        );
        $controllers->get(
            '/{blogpost_id}/authors/', 
            array($this, 'getAllAuthors')
        );
        $controllers->get(
            '/{blogpost_id}/authors/{author_id}/', 
            array($this, 'getOneAuthor')
        );
        $controllers->get(
            '/{blogpost_id}/comments/', 
            array($this, 'getAllComments')
        );
        $controllers->get(
            '/{blogpost_id}/comments/{comment_id}/', 
            array($this, 'getOneComment')
        );
        $controllers->get(
            '/{blogpost_id}/tags/', 
            array($this, 'getAllTags')
        );
        $controllers->get(
            '/{blogpost_id}/tags/{tag_id}/', 
            array($this, 'getOneTag')
        );

        return $controllers;
    }
}

In this example, I left out the other methods, but you get the general idea.

As you can imagine, you might want to fetch comments as a first class API resource (e.g. api_url/comments/. Comments would be mapped to a comments controller. This controller would have much of the same logic as the getAllComments, getOneComment methods of the blog controller. This is a bad thing when you keep the DRY principle in mind.

A better solution to this problem would be to forward the getAllComments and getOneComment methods from the blog controller to the comments controller. Let me show you how this can be achieved.

1) create a separate comments controller which has a getAllComments and getOneComments methods.

namespace App\Controller;

use \Silex\Application,
    \Silex\ControllerProviderInterface;

class Comments implements ControllerProviderInterface
{
    public function connect(Application $application)
    {
        $controllers = $app['controllers_factory'];

        $controllers->get('/', array($this, 'getAllComments'));
        $controllers->get('/{comment_id}/', array($this, 'getOneComment'));

        return $controllers;
    }

    // ... other methods

}

2) map /comments to the comments controller
$application = new \Silex\Application();
// ... /blog
$application->mount('/comments', new \App\Controller\Comments

3) In the blog controller, forward requests to /blog/blogid/comments/ & /blog/blogid/comments/comment_id/ to the new comments controller.

namespace App\Controller;

use \Silex\Application,
    \Silex\ControllerProviderInterface,
    \Symfony\Component\HttpFoundation\Request,
    \Symfony\Component\HttpKernel\HttpKernelInterface;

class Blog implements ControllerProviderInterface
{
    protected $app;

    public function connect(Application $application)
    {
        $this->app   = $application;
        $controllers = $this->app['controllers_factory'];

        ...
        $controllers->get(
            '/{blogpost_id}/comments/', 
            array($this, 'forwardToComments')
        );
        $controllers->get(
            '/{blogpost_id}/comments/{comment_id}/', 
            array($this, 'forwardToComments')
        );
        ...

        return $controllers;
    }

    public function forwardToComments($blogpost_id, $comment_id = null)
    {
        $request = $this->app['request'];
        $query   = array('blogpost_id' => $blogpost_id);
        $uri     = $request->getUriForPath('/comments').'/';
        $uri    .= $comment_id ? $comment_id.'/' : '';

        $query = '?' . http_build_query(array_merge(
            $request->query->all(),
            $query
        ));

        $subRequest = Request::create(
            $uri.$query,
            $request->getMethod(),
            $request->request->all(),
            $request->cookies->all(),
            $request->files->all(),
            $request->server->all()
        );

        return $this->app->handle(
            $subRequest, 
            HttpKernelInterface::SUB_REQUEST, 
            false
        );
    }
}

That's it, you only have to create a new Request instance with the right parameters and let Silex\Application handle it.