If I have a silex route like:
$app->get('/project/{projectName}', function (Request $request, $projectName) use ($app) {
return $projectName;
})
->value('projectName', 'all')
->bind('project');
Why can't I define another route like:
$app->get('/projects', ...)
->bind('projects');
Whenever I try to access the /projects route I get redirected to /projects/ and shown an error message (NotFoundHttpException: No route found for "GET /projects/").
Is there some pluralization logic under the hood that prevents this or what else could be interfering here? (When renaming the second route to anything else it works just fine, so there is something specific to this project/projects naming, I assume.)
This is really strange. I tried this:
$app->get('/project/{projectName}', function (Request $request, $projectName) use ($app) {
return $projectName;
})
->value('projectName', 'all')
->bind('project');
$app->get('/projects', function(Request $request) use ($app) {
return 'TEST';
})
->bind('projects');
And calling /projects in the browser works for me. I get 'TEST' printed. This should work. Check your strings, maybe you misspelled something.
*EDIT 19.09.2013 *
You may also consider modifying your route matching by regular expressions. For this you can use the assert method. Here is an example:
$app->get('/{value}', function(Request $request) use ($app) {
return 'TEST';
})
->bind('projects')
->assert('value', '(projects)/{0,1}');
By using this method, I get 'TEST' when opening both /projects and /projects/. It all depends on what you want to achieve. Another helpful article might be How to allow slashes in routes (from the Symfony documentation)
Related
After upgrading to Slim v4 I'm trying to replace my simple $app->subRequest call with $app->handle as specified in the changelog. However there are no details on how to do this in either the changelog or upgrade guide and my best effort to fix it ends up creating an infinite loop:
$app->get("/foo", function (Request $req) use ($app) {
$uri = $req->getUri();
$newUri = $uri->withPath("/bar");
$barReq = $req->withUri($newUri);
// Here we get stuck in endless loop instead of ending up in the /bar route handler below
$app->handle($barReq);
});
$app->get("/bar", function (Request $req) use ($app) {
echo 'bar!';
die;
});
It's like even though $barReq is a new request object with a completely new uri (and path) the router does not resolve which route handler that should handle it, instead it's just handled by the same one again.
My previous simplified (v3) code looked like and worked fine to get the result of the /bar route when calling /foo:
$app->get("/foo", function (Request $req) use ($app) {
$app->subRequest('GET', '/bar');
});
I'm probably missing some central concept on how Slim 4 handles requests and routes internally and would appreciate some help!
Edit: Should perhaps add that what I mean with internal redirect is that client should not be aware that a redirect has been made. I.e. any regular redirect function returning something to client is not applicable here.
As #remy stated, use the ServerRequestFactory implementing ServerRequestFactoryInterface.
For slim/psr7 it is: Slim\Psr7\Factory\ServerRequestFactory
A silent redirect to another route is then as simple as:
use Slim\Psr7\Factory\ServerRequestFactory;
...
...
$app->get('/foo', function ($request, $response, $args)
{
global $app;
return $app
->handle((new ServerRequestFactory())->createServerRequest('GET', '/bar'));
});
I have two POST routes /test and /test_new. The objective is to redirect all incoming requests from /test_new to /test along with the body contents. Hence The following code aims to use a named route for redirection
$app->post('/test', function (Request $request, Response $response, $args) {
$response->getBody()->write($request->getBody()->getContents());
return $response;
})->setName('test');
$app->post('/test_new', function (Request $request, Response $response, $args) use ($app) {
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
// $routeParser = $app->getRouteCollector()->getRouteParser();
return $response->withStatus(307)->withBody($request->getBody())
->withHeader('Location', $routeParser->urlFor('test'));
});
This piece of code fails with the message
/test_new - Uncaught RuntimeException: Cannot create RouteContext before routing has been completed in /home/biswa/test/test-slim/vendor/slim/slim/Slim/Routing/RouteContext.php:40
If I simply replace the $routeParser with the commented line in the second route handler and use the $app for the RouterParser it works fine. But In my actual code I have the handling logic in a class function and have no access to $app in there.
Can someone please help me to sort this out
Missed the loading of RoutingMiddleware as stated here
I have a single domain/subdomain project. In order to see the event by slug, I made this route:
Route::prefix('events')->namespace('Content\Controller')->group(function () {
Route::get('/', 'EventController#getIndex')->name('event.index');
Route::get('{slug}', 'EventController#getView')->name('event.show');
Route::get('{slug}/edit', 'EventController#getEdit')->name('event.edit');
Route::post('load-more-ajax/{region?}', 'EventController#postLoadMoreAjax');
Route::any('sorted-ajax/{region?}', 'EventController#anySortedAjax');
Route::get('category/{category_slug}/{subcategory_slug?}', 'EventController#getCategory');
});
After my page didn't load correctly, I did a dump in the controller:
public function getView($slug)
{
return $slug;
}
To get to the route I am using this URL: https://example.com/events/slug-example.
The problem is that the route is being hit as I see the response when I change it, but I am not getting the slug, instead I am getting Region object back.
If I do this:
public function getView($region, $slug)
{
return $slug;
}
Then I get the slug back. But I have no idea how is this possible, and how could I do it (I came as another dev on the existing project).
I tried commenting out all the middleware and it is still the same. How can I even make something fill the method if I didn't explicitly say it?
EDIT
I noticed there is binding going on in routes file:
Route::bind('region', function ($value) {
...
});
Now if I dd($value) I get the variable back. How is this value filled? From where could it be forwarded?
Looking quickly it should work, but maybe you was verifying other url.
Make sure you put:
Route::get('{slug}', 'EventController#getView')->name('event.show');
Route::get('{slug}/edit', 'EventController#getEdit')->name('event.edit');
routes at the end of routes you showed.
EDIT
If you think that's not the case and you don't have your routes cached you should run:
php artisan route:list
to verify your routes.
EDIT2
After explaining by OPs in comment, domain used for accessing site is:
{region}.example.com
So having $region in controller as 1st parameter is correct behaviour because of route model binding and other route parameters will be 2nd, 3rd and so on.
Instead of
Route::prefix('events')->namespace('Content\Controller')->group(function () {
Route::get('/', 'EventController#getIndex')->name('event.index');
Route::get('{slug}', 'EventController#getView')->name('event.show');
Route::get('{slug}/edit', 'EventController#getEdit')->name('event.edit');
Route::post('load-more-ajax/{region?}', 'EventController#postLoadMoreAjax');
Route::any('sorted-ajax/{region?}', 'EventController#anySortedAjax');
Route::get('category/{category_slug}/{subcategory_slug?}', 'EventController#getCategory');
});
try
Route::prefix('events')->namespace('Content\Controller')->group(function () {
Route::get('/', 'EventController#getIndex')->name('event.index');
Route::post('load-more-ajax/{region?}', 'EventController#postLoadMoreAjax');
Route::any('sorted-ajax/{region?}', 'EventController#anySortedAjax');
Route::get('category/{category_slug}/{subcategory_slug?}', 'EventController#getCategory');
Route::get('{slug}', 'EventController#getView')->name('event.show');
Route::get('{slug}/edit', 'EventController#getEdit')->name('event.edit');
});
I have just started playing with Laravel framework and I have seen this :
Route::get('foo', function () {
return 'Hello World';
});
Can some one please explain what is this ? I mean over all I know what is get . but why do we put 'foo' and then the closure we put ?
Also where am I really getting the information from ?
First we declare the Facade of the Route, think like a shortcut to use the Route class.
After that, we choose the method of the route, it could be:
Route::get($uri, $callback); //get
Route::post($uri, $callback); //post
Route::put($uri, $callback); //put
Route::patch($uri, $callback); //patch
Route::delete($uri, $callback); //delete
Now you choose the url of the page, for example:
If you digit in the browser:
www.foobar.com/user/profile
Laravel will search for the route with the user/profile parameter, like that:
Route::get('user/profile', function () {
return 'Hello World';
});
You can pass variables too,
Route::get('user/{id}', function () {
return 'Hello World';
});
After that, you choose the callback method, in other words, what is gonna happen when the laravel enter in the route.
In your example, you have the function example, just returning a simple "hello world".
The best pratice here is to create a controller
php artisan make:controller FoobarController --resource
And referece to any method of your controller
Route::get('user/profile', 'FoobarController#index');
Now, when the laravel find the route, it's going to redirect to the index method of the Foobar controller, and there, you define your logic
public function index() {
return view('welcome');
}
Firsty, read the documentation, it's super easy, even for the begginers.
Step by step:
get is the HTTP method you use on this particular route. The other most often used is POST, but there are more of them.
foo is the route, in that case will be: www.example.com\foo. You can put any name as you want and need.
As a second parameter to a Route facade you put closure/name of the controller/view you want to handle endpoint, e.g.
Route::get('foo', 'SomeController#method');
Route::get('foo', function(){
return view('some.view');
};
There are lot more options in routing and they are not difficult to understand, just have a look on documentation or some video tutorials.
Is it possible to forward a request in Slim?
The meaning of "forward", like in JavaEE, is to internally redirect to another route without return the response to the client and maintaining the model.
For example:
$app->get('/logout',function () use ($app) {
//logout code
$app->view->set("logout",true);
$app->forward('login'); //no redirect to client please
})->name("logout");
$app->get('/login',function () use ($app) {
$app->render('login.html');
})->name("login");
In my opinion, the best way to do this would be by using Slim's internal router (Slim\Router) capabilities and dispatching (Slim\Route::dispatch()) the matched route (meaning: executing the callable from a matched route without any redirect). There are a couple of options that come to mind (depending on your setup):
1. calling a named route + callable doesn't take any arguments (your example)
$app->get('/logout',function () use ($app) {
$app->view->set("logout",true);
// here comes the magic:
// getting the named route
$route = $app->router()->getNamedRoute('login');
// dispatching the matched route
$route->dispatch();
})->name("logout");
This should definitely do the trick for you, but I still want to show the other scenarios ...
2. calling a named route + callable with arguments
The above example will fail ... because now we need to pass arguments to the callable
// getting the named route
$route = $app->router()->getNamedRoute('another_route');
// calling the function with an argument or array of arguments
call_user_func($route->getCallable(), 'argument');
Dispatching the route (with $route->dispatch()) will invoke all middleware, but here we are just calling the the callable directly ... so to get the full package we should consider the next option ...
3. calling any route
Without named routes we can get a route by finding the one matching the a http method and pattern. For this we use Router::getMatchedRoutes($httpMethod, $pattern, $reload) with reload set to TRUE.
// getting the matched route
$matched = $app->router()->getMatchedRoutes('GET','/classes/name', true);
// dispatching the (first) matched route
$matched[0]->dispatch();
Here you might want to add some checks and for example dispatch notFound in case no route is matched.
I hope you get the idea =)
There is redirect() method. However it sends an 302 Temporary Redirect response which you do not want.
$app->get("/foo", function () use ($app) {
$app->redirect("/bar");
});
Another possibility is pass() which tells application to continue to next matching route. When pass() is called Slim will immediately stop processing the current matching route and invoke the next matching route.
If no subsequent matching route is found, a 404 Not Found is sent to the client.
$app->get('/hello/foo', function () use ($app) {
echo "You won't see this...";
$app->pass();
});
$app->get('/hello/:name', function ($name) use ($app) {
echo "But you will see this!";
});
I think you have to redirect them. There is no forward in Slim. But you can set a status code for example in the redirect function. When you redirect to a route you should get that functionality you want.
// With route
$app->redirect('login');
// With path and status code
$app->redirect('/foo', 303);
here is an example from the documentation:
<?php
$authenticateForRole = function ( $role = 'member' ) {
return function () use ( $role ) {
$user = User::fetchFromDatabaseSomehow();
if ( $user->belongsToRole($role) === false ) {
$app = \Slim\Slim::getInstance();
$app->flash('error', 'Login required');
$app->redirect('/login');
}
};
};