I have a question : so for examples I have an app in symfony3 which have the following routes : /admin/login,admin/news,admin/gallery, but the route /admin/authentification doesn't exist. So the idea is if the route doesn't exist I want to redirect the user to homepage /. Can you help me please ? Thanks in advance and sorry for my english
I'm not confident this is the best solution, but you can use a UrlMatcher to check that the URL you're passing correlates to an available route:
/**
* #Route("/debug")
*/
public function DebugAction(){
$router = $this->get('router');
//Get all the routes that exist.
$routes = $router->getRouteCollection();
$context = $router->getContext();
$urlMatcher = new UrlMatcher($routes, $context);
$url = '/admin/login';
try{
//UrlMatcher::match() will throw a ResourceNotFoundException if the route
//doesn't exist.
$urlMatcher->match($url);
return $this->redirect($url);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e){
return $this->redirect('/');
}
}
I'm not particularly keen on this solution because it relies on catching an exception, rather than checking a boolean value to determine if the route exists.
You can check if the rout exist.
function routeExists($name)
{
// I assume that you have a link to the container in your twig extension class
$router = $this->container->get('router');
return (null === $router->getRouteCollection()->get($name)) ? false : true;
}
And depending on result, do the redirect to the rout, or to the default webpage, or whatever you need.
Related
In my web file, I have a route that accepts a $id as a value to be passed to a function within my PagesController. However, I want the function to still execute and show the intended form even when the $id is not passed to the function.
web.php file
Route::get('/request/{id}', 'PagesController#makeRequest');
PagesController.php file
public function makeRequest($id)
{
if(!empty($id)){
$target = Partner::find($id);
}
return view('pages.makeRequest')->with('target', $target);
}
makeRequest.blade.php
<input type="text" class="form-control" value="{{$target->inst_name}}" required disabled>
I want the page to display details from the database with the $id when provided or have empty spaces when the $id isn't provided.
As the Laravel Documentation states: Use Optional Parameters like this:
Route::get('/request/{id?}', 'PagesController#makeRequest'); //Optional parameter
Controller
public function makeRequest($id = null)
{
if(!empty($id)){
$target = User::find($id);
return view('pages.makeRequest')->with('target', $target);
} else {
return view('pageslist'); ///set default list..
}
}
This is the way I did it:
Route::get('index', 'SeasonController#index');
// controller
public function index(Request $request )
{
$id= $request->query('id');
}
The way you call it:
localhost/api/index?id=7
All your solutions were helpful. The main thing was that when I called just the view without passing $target to the view, the page displayed an error. So this is what I did.
Route::get('/request/{id?}', 'PagesController#makeRequest');
Then in the controller,
public function makeRequest(Request $request, $id=null)
{
if ($id != null) {
$target = Partner::find($id);
return view('pages.makeRequest')->with('target', $target);
}
return view('pages.makeNullRequest');
}
If you didn't understand what happened, I created a new view which had this instead of what I had posted in the question.
<input type="text" class="form-control" value="" required readonly>
Sorry I didn't update you guys in time. I think Jignesh Joisar came closest to helping me solve this issue. really appreciate all you guys. You're just awesome
You can use optional parameter :
Route::get('/request/{id?}', 'PagesController#makeRequest');
Now, as the parameter is optional, while defining the controller function you need to assign its default value to null in argument declaration.
<?php
public function makeRequest($id = null)
{
if($id){
$target = Partner::findOrFail($id);
return view('pages.makeRequest')->with(compact('target'));
}
// Return different view when id is not present
// Maybe all targets if you want
$targets = Partner::select('column1', 'column2')->get();
return view('pages.all')->with('targets');
}
I am using findOrFail instead of find. Its Laravel's very handy function which automatically throws a ModelNotFound exception and for frontend user throws a simple 404 page.
So if anyone is accessing www.url.com/request/2, its a valid id then it will show a valid page with data. If the accessed url is www.url.com/request/blahblah then it will throw 404. It avoids efforts of handling this manually.
For optional parameter pass id with ? in route and give $id = null in your function's parameter like this:
Route::get('/request/{id?}', 'PagesController#makeRequest'); //Optional parameter
makeRequest($id = null) {
// Code here...
...
}
in your routes file (web.php , as mentioned in your question)
Route::get('/request/{id?}', 'PagesController#makeRequest');
and in your controller PagesController.php
public function makeRequest($id = null)
{
}
To read more about this, just read https://laravel.com/docs/5.7/routing#parameters-optional-parameters
For me the answer was in the order that I listed the Routes in the routes file.
The routes file will call the first one that matches the pattern.
Route::get('/ohmy/{id?}', 'OhMyController#show');
Route::get('/ohmy/all', 'OhMyController#all'); //never gets called
Instead, put optional parameters at end of list:
Route::get('/ohmy/all', 'OhMyController#all');
Route::get('/ohmy/{id?}', 'OhMyController#show');
the answer has been said. just a side note: optional parameters won't work if you are using resource routes.
for example:
Route::resource('items',itemController::class)->except([
'create',
]);
Route::get('/items/create/{category_id?}',function($category_id = 'abc'){
dd($category_id);
});
if i go to " items/create/1 ", the result will be "1".
if i go to " items/create ", it will return 404. ( but we expect it to say "abc".)
this happens because other routes that start with "items" are expected to be generated from "resource" functionality.
so if you use resource routes, you should consider that.
I'm new to Laravel and wondering if anyone can assist with wildcards in routes.php file
This is what I currently have
Route::post('/post/method1','postController#method1');
Route::post('/post/method2','postController#method2');
Route::post('/post/method3','postController#method3');
This is what I'm trying to achieve.
Route::post('/post/{variable}','postController#{variable}');
Help greatly appreciated
Create a method in the controller to route the request based on the parameter value:
public function route(string $path){
// define the allowed methods
$allowedMethods = ['someMethodName', 'anotherMethodName'];
// check if the path name is a method and is allowed
if(in_array($path, $allowedMethods, true) && method_exists($this, $path)){
// call method
return $this->{$path}();
}
// handle error
}
Then just use the controller like you would do normally:
Route::post('/post/{variable}','postController#route');
I just upgraded to Laravel 4.1 and can no longer use a function I was using in the past. I wrote a function to redirect an incoming request to another route, get the result, and replace the current route with the original incoming route. I used this on my frontend controllers to consume my own API which is defined in the same application.
Here is the function:
public static function redirectRequest($newRoute, $verb, $args = null)
{
// store the original request data and route
$originalInput = Request::input();
$originalRoute = Route::current();
$request = $args === null ? Request::create($newRoute, $verb) : Request::create($newRoute, $verb, $args);
// replace the request input for the new route...
Request::replace($request->input());
try
{
$response = Route::dispatch($request);
return $response;
}
catch (\Exception $e)
{
throw $e;
}
finally
{
// replace the request input and route back to the original state
Request::replace($originalInput);
Route::setCurrentRoute($originalRoute);
}
}
And I would use it like:
Helpers::redirectRequest('/api/v1/someroute', 'GET');
The problem is that, when I try to return things to the way they were before the redirect, I can't. setCurrentRoute has been removed from 4.1 and I can't figure out how to reset the current route.
One thing that I have done in the past is used the actual routes.php to handle that. If you register a "catchall" route as:
Route::get('api/v1', 'ApiController#route');
As long as this is after all other routes (Laravel uses the first matching route) you can then handle that within your ApiController as
public function route($uri) {
// Handle your API route using the $uri variable
}
This may not be the solution that you are looking for, but I have found it very convenient.
I'm having a small problem when trying to flash a message and redirect the user back to the previous page in Symfony 2.
I have a very simple CRUD. When new, or edit, i want to flash a message if something goes wrong in the respective create/update methods:
User --GET--> new
new --POST--> create (fails)
--REDIRECT--> new (with flash message)
I'm doing the following:
$this->container->get('session')->setFlash('error', 'myerror');
$referer = $this->getRequest()->headers->get('referer');
return new RedirectResponse($referer);
However, it's not redirecting to the correct referrer! Even though the value of referrer is correct (eg.: http://localhost/demo/2/edit/) It redirects to the index. Why?
This is an alternative version of Naitsirch and Santi their code. I realized a trait would be perfect for this functionality. Also optimized the code somewhat. I preferred to give back all the parameters including the slugs, because you might need those when generating the route.
This code runs on PHP 5.4.0 and up. You can use the trait for multiple controllers of course. If you put the trait in a seperate file make sure you name it the same as the trait, following PSR-0.
<?php
trait Referer {
private function getRefererParams() {
$request = $this->getRequest();
$referer = $request->headers->get('referer');
$baseUrl = $request->getBaseUrl();
$lastPath = substr($referer, strpos($referer, $baseUrl) + strlen($baseUrl));
return $this->get('router')->getMatcher()->match($lastPath);
}
}
class MyController extends Controller {
use Referer;
public function MyAction() {
$params = $this->getRefererParams();
return $this->redirect($this->generateUrl(
$params['_route'],
[
'slug' => $params['slug']
]
));
}
}
For symfony 3.0,flash message with redirection back to previous page,this can be done in controller.
$request->getSession()
->getFlashBag()
->add('notice', 'success');
$referer = $request->headers->get('referer');
return $this->redirect($referer);
The message from Naitsirch presented in the next url:
https://github.com/symfony/symfony/issues/2951
Seems a good solution for that you need:
public function getRefererRoute()
{
$request = $this->getRequest();
//look for the referer route
$referer = $request->headers->get('referer');
$lastPath = substr($referer, strpos($referer, $request->getBaseUrl()));
$lastPath = str_replace($request->getBaseUrl(), '', $lastPath);
$matcher = $this->get('router')->getMatcher();
$parameters = $matcher->match($lastPath);
$route = $parameters['_route'];
return $route;
}
Then with a redirect:
public function yourFunctionAction()
{
$ruta = $this->getRefererRoute();
$locale = $request->get('_locale');
$url = $this->get('router')->generate($ruta, array('_locale' => $locale));
$this->getRequest()->getSession()->setFlash('notice', "your_message");
return $this->redirect($url);
}
This works for me:
$this->redirect($request->server->get('HTTP_REFERER'));
I have similar functionality on my site. It is multilingual. Articles exists only in a single locale. When the user will try to switch to other locale, it should redirect back to previous page and flash message that that page/article doesn't exist on the requested locale.
/en/article/3 -> /fr/article/3 (404) -> Redirect(/en/article/3)
Here is my version of the script that works well on dev and prod environments:
$referer = $request->headers->get('referer')
// 'https://your-domain.com' or 'https://your-domain.com/app_dev.php'
$base = $request->getSchemeAndHttpHost() . $request->getBaseUrl();
// '/en/article/3'
$path = preg_replace('/^'. preg_quote($base, '/') .'/', '', $referer);
if ($path === $referer) {
// nothing was replaced. referer is an external site
} elseif ($path === $request->getPathInfo()) {
// current page and referer are the same (prevent redirect loop)
} else {
try {
// if this will throw an exception then the route doesn't exist
$this->container->get('router')->match(
// '/en/hello?foo=bar' -> '/en/hello'
preg_replace('/\?.*$/', '', $path)
);
// '/app_dev.php' . '/en/article/3'
$redirectUrl = $request->getBaseUrl() . $path;
return new RedirectResponse($redirectUrl);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {}
}
I just set up a simple app, and it seems to work fine. My createAction() looks like this:
public function createAction()
{
$entity = new Pokemon();
$request = $this->getRequest();
$form = $this->createForm(new PokemonType(), $entity);
$form->bindRequest($request);
if ($entity->getName() == "pikachu")
{
$this->container->get("session")->setFlash("error", "Pikachu is not allowed");
$url = $this->getRequest()->headers->get("referer");
return new RedirectResponse($url);
}
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('pokemon_show', array('id' => $entity->getId())));
}
return $this->render('BulbasaurBundle:Pokemon:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
The flow goes:
User navigates to /new
User enters invalid option of "pikachu"
User clicks submit (POSTs to /create)
Application rejects the entry, adds flash message, and redirects back to /new
User sees /new with the flash message
A few things to check:
Is your route for /demo/{entityId}/edit actually working? (i.e. if you enter it in the browser, does it actually go to where you expect it to?)
Are you chaining together different redirects/forwards? I've noticed that I get unexpected (but correct) behavior when I have a controller that redirects to a URL, and the controller responsible for that URL also redirects somewhere else. I've fixed this issue by using forwards instead.
That said, if all else fails, you could just use the controller's redirect() method to manage the redirect:
public function createAction()
{
...
return $this->redirect($this->generateUrl("pokemon_new"));
...
}
Here you go, declare this as a service and it will return referer to you wherever and whenever you need it. No traits, no weird dependencies.
class Referer
{
/** #var RequestStack */
private $requestStack;
/** #var RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router)
{
$this->requestStack = $requestStack;
$this->router = $router;
}
public function getReferer() : string
{
$request = $this->requestStack->getMasterRequest();
if (null === $request)
{
return '';
}
//if you're happy with URI (and most times you are), just return it
$uri = (string)$request->headers->get('referer');
//but if you want to return route, here you go
try
{
$routeMatch = $this->router->match($uri);
}
catch (ResourceNotFoundException $e)
{
return '';
}
$route = $routeMatch['_route'];
return $route;
}
}
seems like you need to have a payload for your redirect to point to. it seems like obscure concept code to me. I would also advise you to make sure your configuration files point to the correct redirect code snippet. Check your server access file to make sure it has redirects enabled also.
This situation arises from someone wanting to create their own "pages" in their web site without having to get into creating the corresponding actions.
So say they have a URL like mysite.com/index/books... they want to be able to create mysite.com/index/booksmore or mysite.com/index/pancakes but not have to create any actions in the index controller. They (a non-technical person who can do simple html) basically want to create a simple, static page without having to use an action.
Like there would be some generic action in the index controller that handles requests for a non-existent action. How do you do this or is it even possible?
edit: One problem with using __call is the lack of a view file. The lack of an action becomes moot but now you have to deal with the missing view file. The framework will throw an exception if it cannot find one (though if there were a way to get it to redirect to a 404 on a missing view file __call would be doable.)
Using the magic __call method works fine, all you have to do is check if the view file exists and throw the right exception (or do enything else) if not.
public function __call($methodName, $params)
{
// An action method is called
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, -6);
// We want to render scripts in the index directory, right?
$script = 'index/' . $action . '.' . $this->viewSuffix;
// Script file does not exist, throw exception that will render /error/error.phtml in 404 context
if (false === $this->view->getScriptPath($script)) {
require_once 'Zend/Controller/Action/Exception.php';
throw new Zend_Controller_Action_Exception(
sprintf('Page "%s" does not exist.', $action), 404);
}
$this->renderScript($script);
}
// no action is called? Let the parent __call handle things.
else {
parent::__call($methodName, $params);
}
}
You have to play with the router
http://framework.zend.com/manual/en/zend.controller.router.html
I think you can specify a wildcard to catch every action on a specific module (the default one to reduce the url) and define an action that will take care of render the view according to the url (or even action called)
new Zend_Controller_Router_Route('index/*',
array('controller' => 'index', 'action' => 'custom', 'module'=>'index')
in you customAction function just retrieve the params and display the right block.
I haven't tried so you might have to hack the code a little bit
If you want to use gabriel1836's _call() method you should be able to disable the layout and view and then render whatever you want.
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
I needed to have existing module/controller/actions working as normal in a Zend Framework app, but then have a catchall route that sent anything unknown to a PageController that could pick user specified urls out of a database table and display the page. I didn't want to have a controller name in front of the user specified urls. I wanted /my/custom/url not /page/my/custom/url to go via the PageController. So none of the above solutions worked for me.
I ended up extending Zend_Controller_Router_Route_Module: using almost all the default behaviour, and just tweaking the controller name a little so if the controller file exists, we route to it as normal. If it does not exist then the url must be a weird custom one, so it gets sent to the PageController with the whole url intact as a parameter.
class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
{
private $_catchallController = 'page';
private $_catchallAction = 'index';
private $_paramName = 'name';
//-------------------------------------------------------------------------
/*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
with the following changes:
- if the path includes a valid module, then use it
- if the path includes a valid controller (file_exists) then use that
- otherwise use the catchall
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
$file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php';
if (file_exists( $file ))
{
$values[$this->_controllerKey] = array_shift($path);
}
else
{
$values[$this->_controllerKey] = $this->_catchallController;
$values[$this->_actionKey] = $this->_catchallAction;
$params[$this->_paramName] = join( self::URI_DELIMITER, $path );
$path = array();
}
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
}
So my MemberController will work fine as /member/login, /member/preferences etc, and other controllers can be added at will. The ErrorController is still needed: it catches invalid actions on existing controllers.
I implemented a catch-all by overriding the dispatch method and handling the exception that is thrown when the action is not found:
public function dispatch($action)
{
try {
parent::dispatch($action);
}
catch (Zend_Controller_Action_Exception $e) {
$uristub = $this->getRequest()->getActionName();
$this->getRequest()->setActionName('index');
$this->getRequest()->setParam('uristub', $uristub);
parent::dispatch('indexAction');
}
}
You could use the magic __call() function. For example:
public function __call($name, $arguments)
{
// Render Simple HTML View
}
stunti's suggestion was the way I went with this. My particular solution is as follows (this uses indexAction() of whichever controller you specify. In my case every action was using indexAction and pulling content from a database based on the url):
Get an instance of the router (everything is in your bootstrap file, btw):
$router = $frontController->getRouter();
Create the custom route:
$router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));
Pass the new route to the front controller:
$frontController->setRouter($router);
I did not go with gabriel's __call method (which does work for missing methods as long as you don't need a view file) because that still throws an error about the missing corresponding view file.
For future reference, building on gabriel1836 & ejunker's thoughts, I dug up an option that gets more to the point (and upholds the MVC paradigm). Besides, it makes more sense to read "use specialized view" than "don't use any view".
// 1. Catch & process overloaded actions.
public function __call($name, $arguments)
{
// 2. Provide an appropriate renderer.
$this->_helper->viewRenderer->setRender('overload');
// 3. Bonus: give your view script a clue about what "action" was requested.
$this->view->action = $this->getFrontController()->getRequest()->getActionName();
}
#Steve as above - your solution sounds ideal for me but I am unsure how you implmeented it in the bootstrap?