How to address variable number of arguments in laravel routes to controllers - laravel

I'm new to Laravel and I did something like this for my routes:
Route::get('/fundraisers/{id}', 'FundraiserController#index')->name('fundraisers.list');
Route::get('/{locale}/fundraisers/{id}', 'FundraiserController#indexLang')->name('fundraisers.list.lang')->where(['locale'=>$loc=['fr,'zh']);
And then I need two methods in my controller:
index($id) {
// Load record by id and language == 'en'
}
indexLang($locale, $id) {
// Load record by id and language == $locale
}
I read elsewhere that there's no way around explicitly writing two route rules if I want English urls to be the only exception to not appearing in the url.
Now when I write a route like this:
Route::get('/fundraisers/{id}', 'FundraiserController#indexLang')->name('fundraisers.list');
What happens is that $locale is a number and $id is null in indexLang. Is there a way to do something like assign {id} to a $_REQUEST['id'] and have $_REQUEST['locale'] be unset? Like the way you would have it with .htaccess url rewrite rules? That way I can get rid of index($id) since it's redundant exception of indexLang

I don't see anything wrong with your first attempt, except for the where which needs to be a regular expression.
Route::get('/{locale}/fundraisers/{id}', 'FundraiserController#indexLang')->name('fundraisers.list.lang')->where(['locale' => 'fr|zh']);
So try that.
The reason in the second attempt for "$locale is a number and $id is null" is because you're only capturing one in the route i.e. '/fundraisers/{id}' which is then being passed to the controller's first variable, in this case $locale.

Related

Laravel Routing - Best way to differentiate routes with same format

Say I have a database of items, each belonging to a country, county and a city. I want to have routes to list all of the items within a singular country, county or a city. Each country/county/city has a slug, for example france for France that is to be used in URL.
I want all the routes to have the same format:
/items-in-{slug}, so for example /items-in-france or /items-in-paris.
However, the slug can be a slug of one of multiple Models. What is the best set up for this sort of situation? I can think of 3 main options:
A single route that will catch all matching URLs, which will run a specialised RoutingController or similar, which will then in turn check which Model slug represents and propogate to the correct controller method (for example, viewInCountry($slug) or viewInCity($slug))
One route for each type of Model, and putting restrictions on each route that would only accept one of the existing slugs (i.e. fetch all of the slugs and generate a regex that will only accept one of the existing slugs)
Fetch all Models (countries/cities/counties) and generate a Route for each one
All options seem a little hacky and I am wondering if there is a more elegant solution to this.
I would go for the second option, except don't use a regex to accept existing slugs. Instead you could write your own model route binding resolution logic as mentioned here under 'Customizing The Resolution Logic'. Something like this could probably do the trick:
// Put this in your RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::bind('slug', function ($value) {
$country = App\Country::where('slug', $value)->first();
if ($country !== null) {
return $country;
}
$city = App\City::where('slug', $value)->first();
if ($city !== null) {
return $city;
}
// Repeat for each model.
// 404 in case no model has been matched.
abort(404).
});
}
Alternatively if you are willing to adjust the url a bit, then you could create a route and a controller per model. That would require you to have urls like /items/france or items/paris etc.

Laravel Routes and 'Class#Method' notation - how to pass parameters in URL to method

I am new to Laravel so am uncertain of the notation. The Laravel documentation shows you can pass a parameter this way:
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
Which is very intuitive. Here is some existing coding in a project I'm undertaking, found in routes.php:
Route::get('geolocate', 'Api\CountriesController#geolocate');
# which of course will call geolocate() in that class
And here is the code I want to pass a variable to:
Route::get('feed/{identifier}', 'Api\FeedController#feed');
The question being, how do I pass $identifier to the class as:
feed($identifier)
Thanks!
Also one further sequitir question from this, how would I notate {identifier} so that it is optional, i.e. simply /feed/ would match this route?
You should first create a link which looks like:
/feed/123
Then, in your controller the method would look like this:
feed($identifier)
{
// Do something with $identifier
}
Laravel is smart enough to map router parameters to controller method arguments, which is nice!
Alternatively, you could use the Request object to return the identifier value like this:
feed()
{
Request::get('identifier');
}
Both methods have their merits, I'd personally use the first example for grabbing one or two router parameters and the second example for when I need to do more complicated things.

Laravel 5.1 optional route params in the middle of the URL

Is there any way I can use optional route params in the middle of the URL in Laravel 5. Here is what I want
Route::get('api/{locale?}/my-url', 'MyController#myAction');
You can't have optional route parameters in the middle of the route path, because they make the definition ambiguous if they are omitted, and the route won't be matched.
You could have two route definitions one with and one without (as you've suggested in your comment):
Route::get('api/{locale}/my-url', 'MyController#myAction');
Route::get('api//my-url', 'MyController#myAction');
But if you have lots of routes you'll have a lot of duplicates just for this.
You could just leave one definition with the locale, since there's no big deal of passing the default locale as part of the URL path. So if your default locale is en it just gets passed via the path as other locales:
http://example.com/api/en/my-url
However, since I'm guessing the locale is used for language appropriate responses and is only used for GET/HEAD requests, the best solution that I see here and it makes the most sense, is to just pass the locale as a parameter, because it's essentially an option:
http://example.com/api/my-url?locale=en
That way the Laravel route definition doesn't need to worry about it. Then you can use a middleware to change the locale if it is passed along in the query string. Here's an example of a middleware class that sets the locale and checks if it's an allowed locale:
namespace App\Http\Middleware;
use Closure;
class SetLocale
{
public function handle($request, Closure $next)
{
if ($request->has('locale') && $this->isValidLocale()) {
app()->setLocale($request->input('locale'));
}
return $next($request);
}
protected function isValidLocale()
{
return in_array(request()->input('locale'), ['en', 'es', 'fr', 'de']);
}
}
Now in your controller action you can just use:
app()->getLocale();
And it will be set to the value passed in the query string.
If I understand well and you want the url to work both as api/my-url and api/en/my-url you can simply setup two routes:
Route::get('api/{locale}/my-url', 'MyController#myAction');
Route::get('api/my-url', 'MyController#myAction');

Laravel how to route old urls

I am using Laravel 4.
I have an old url that needs to be routable. It doesn't really matter what it's purpose is but it exists within the paypal systems and will be called regularly but cannot be changed (which is ridiculous I know).
I realise that this isn't the format url's are supposed to take in Laravel, but this is the url that will be called and I need to find a way to route it:
http://domain.com/forum/index.php?app=subscriptions&r_f_g=xxx-paypal
(xxx will be different on every request)
I can't figure out how to route this with laravel, i'd like to route it to the method PaypalController#ipbIpn so i've tried something like this:
Route::post('forum/index.php?app=subscriptions&r_f_g={id}-paypal', 'PaypalController#ipbIpn');
But this doesn't work, infact I can't even get this to work:
Route::post('forum/index.php', 'PaypalController#ipbIpn');
But this will:
Route::post('forum/index', 'PaypalController#ipbIpn');
So the question is how can I route the url, as it is at the top of this question, using Laravel?
For completeness I should say that this will always be a post not a get, but that shouldn't really make any difference to the solution.
Use this:
Route::post('forum/{file}', 'PaypalController#ipbIpn');
And then in the controller, use
public function forum($file) {
$request = Route::getRequest();
$q = (array) $request->query; // GET
$parameters = array();
foreach($q as $key => $pararr) {
$parameters = array_merge($parameters, $pararr);
}
}
You can then access the get parameters via e.g.
echo $parameters['app'];
you can use route redirection to mask and ending .php route ex:
Route::get('forum/index', ['uses'=> 'PaypalController#ipbIpn']);
Route::redirect('forum/index.php', 'forum/index');

How to build an anchor in CodeIgniter where you want to change a variable that is already present in the URI?

Normally I would just use URL GET parameters but CodeIgniter doesn't seem to like them and none of the URL helper functions are designed for them, so I'm trying to do this the 'CodeIgniter way'.
I would like to build a page where the model can accept a number of different URI paramters, none necessarily present, and none having to be in any particular order, much like a regular URL query string with get parameters.
Let's say I have the following url:
http://example.com/site/data/name/joe/
Here not including the controller or the method there would be one parameter:
$params = $this->uri->uri_to_assoc(1);
print_r($params);
// output
array( [name] => [joe] )
If I wanted 'joe' to change to 'ray' I could do this:
echo anchor('name/ray');
Simple enough but what if there are more parameters and the position of the parameters are changing? Like:
http://example.com/site/data/town/losangeles/name/joe/
http://example.com/site/data/age/21/name/joe/town/seattle
Is there a way to just grab the URL and output it with just the 'name' parameter changed?
Edit: As per landons advice I took his script and set it up as a url helper function by creating the file:
application/helpers/MY_url_helper.php
Basically I rewrote the function current_url() to optionally accept an array of parameters that will be substituted into the current URI. If you don't pass the array the function acts as originally designed:
function current_url($vars = NULL)
{
$CI =& get_instance();
if ( ! is_array($vars))
{
return $CI->config->site_url($CI->uri->uri_string());
}
else
{
$start_index = 1;
$params = $CI->uri->uri_to_assoc($start_index);
foreach ($vars as $key => $value)
{
$params[$key] = $value;
}
$new_uri = $CI->uri->assoc_to_uri($params);
return $CI->config->site_url($new_uri);
}
}
It works OK. I think the bottom line is I do not like the 'CodeIgniter Way' and I will be looking at mixing segment based URL's with querystrings or another framework altogether.
You can use the assoc_to_uri() method to get it back to URI format:
<?php
// The segment offset to use for associative data (change me!)
$start_index = 1;
// Parse URI path into associative array
$params = $this->uri->uri_to_assoc($start_index);
// Change the value you want (change me!)
$params['name'] = 'ray';
// Convert back to path format
$new_uri = $this->uri->assoc_to_uri($params);
// Prepend the leading segments back to the URI
for ($i=1; $i<$start_index; $i++)
{
$new_uri = $this->uri->segment($i).'/'.$new_uri;
}
// Output anchor
echo anchor($new_uri);
I'd recommend wrapping this in a helper function of some sort. Happy coding!
Why not use CodeIgniter's built in URI Class? It allows you to select the relevant segments from the URL which you could use to create the anchor. However, unless you created custom routes, it would mean that your methods would need to accept more parameters.
To use the URI Class, you would have the following in your method:
echo anchor($this->uri->segment(3).'/ray');
Assuming /site/data/name are all CodeIgniter specific (/controller/method/parameter)
Now, I think this could be made a lot easier if you were using routes. Your route would look like this:
$route['site/data/name/(:any)'] = 'site/data/$1';
Effictively, your URL can be as detailed and specific as you want it to be, but in your code the function is a lot cleaner and the parameters are quite descriptive. You method would defined like this:
function data($name) { }
To extend your route to accept more parameters, your route for the the example URL "http://example.com/site/data/age/21/name/joe/town/seattle" you supplied would look like this:
$route['site/data/age/(:num)/name/(:any)/town/(:any)'] = 'controller/data/$1/$2/$3';
And your function would look like this:
function data($age, $name, $town) { }

Resources