Issue
I have a cakephp plugin that creates image thumbnails.
Currently, thumbnails are "served" by an action of a controller that returns a file as response (here).
This is the route:
Router::plugin(THUMBER, ['path' => '/thumb'], function (RouteBuilder $routes) {
$routes->get('/:basename', ['controller' => 'Thumbs', 'action' => 'thumb'], 'thumb')
->setPatterns(['basename' => '[\w\d=]+'])
->setPass(['basename']);
});
So the url are these (the thumbnail basename is encoded):
/thumb/ZDc1NTYyMGY1N2VmMzRiNTQyZjE0NTY2Mjk0YWQ2NTFfNGIyYTBkMjVhMTdjZTdjN2E4MjVjY2M1YWU1ODNhMzcuZ2lm
Now I'm trying to replace the controller with a middleware.
This is quite simple, because basically it would work like the AssetMiddleware
and the __invoke()
method is almost like the old action method:
class ThumbnailMiddleware
{
use ThumbsPathTrait;
public function __invoke($request, $response, $next)
{
if ($request->getParam('_name') !== 'thumb' || !$request->getParam('basename')) {
return $next($request, $response);
}
$file = $this->getPath(base64_decode($request->getParam('basename')));
if (!is_readable($file)) {
throw new ThumbNotFoundException(__d('thumber', 'File `{0}` doesn\'t exist', $file));
}
$response = $response->withModified(filemtime($file));
if ($response->checkNotModified($request)) {
return $response;
}
return $response->withFile($file)->withType(mime_content_type($file));
}
}
This works very well.
The problem is that it now works because this middleware takes the controller's route and "intercepts" the request, "resolving" it without going through the controller (see the first three lines of the __invoke()
method).
Now I would like to rewrite that route and untie it from the controller, which I should totally eliminate from the plugin.
Obviously it works well like this (the second parameter is null
):
$routes->get('/:basename', null, 'thumb')
->setPatterns(['basename' => '[\w\d=]+'])
->setPass(['basename']);
Or I could just call the RouteBuilder::fallback()
method and analyze the url of the request (as it happens for the AssetMiddleware
).
But I was wondering if there is a way to link a route only to a middleware and explicitly for a middleware. Or, if not, what is the best method. I know that I can apply "middleware to specific routing scopes" (cookbook), so I'm wondering if this is definitely the correct formula:
Router::plugin(THUMBER, ['path' => '/thumb'], function (RouteBuilder $routes) {
$routes->registerMiddleware('thumbnail', new ThumbnailMiddleware);
$routes->applyMiddleware('thumbnail');
$routes->get('/:basename', null, 'thumb')
->setPatterns(['basename' => '[\w\d=]+'])
->setPass(['basename']);
});
Solution
Well, you are not required to pass any defaults to a route, so from that point of view it's correct, the route wouldn't be "bound" to a controller. If your middleware wouldn't intercept the request, then things would end up with a MissingControllerException
being thrown, as the dispatcher would get null
as the controller name. The resulting error message would probably be a bit misleading, as there would be no controller name to include.
What you are doing there will cause your middleware to apply to all routes in the /thumb
scope, so if there would ever be any other routes, then your middleware would require according parameter checks. You can restrict things further by applying the middleware to that one specific route, instead of the route builder:
// $routes->applyMiddleware('thumbnail'); // don't do that
$routes
->get('/:basename', null, 'thumb')
->setPatterns(['basename' => '[\w\d=]+'])
->setPass(['basename'])
->setMiddleware(['thumbnail']); // do this instead
That way your middleware would only be invoked for that specific route.
Answered By - ndm
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.