Wildcard routes in Laravel? - laravel

Is there a way to have a wildcard route? But only with specific names.
Eg.
I have a number of routes that lead to the same place:
/archive/gallery/1/picture/1
/masters/gallery/1/picture/1
/browse/gallery/1/picture/1
These all load up the same picture, but it would be great if I could do something like this:
Route::get('{???}/gallery/{galleryId}/picture/{pictureId}', array(
'as'=>'picture',
'uses'=>'PictureController#getPicture'
));
But only use archive or masters or browse as the wildcard.

You can't define a different controller, depending on the wildcard. You would have to define that in the controller.
Route::get('{page}/gallery/{galleryId}/picture/{pictureId}', array(
'as'=>'picture',
'uses'=>'PictureController#getPicture'
));
public function getPicture($page)
{
if ($page == "archive")
return View::make('archive');
else if ($page == "browse")
return View::make('browse');
else if ($page == "masters")
return View::make('masters');
}
be sure to place the route at the bottom of the routes file though, otherwise it will override the other routes :) as laravel uses first-in -> first->out

You may try this
Route::get('{type}/gallery/{galleryId}/picture/{pictureId}', array(
'as'=>'picture',
'uses'=>'PictureController#getPicture'
))->where('type', 'masters|browse|archive');
PictureController:
public function getPicture($type, $galleryId, $pictureId)
{
// $type could be only masters or browse or archive
// otherwise requested route won't match
// If you want to load view depending on type (using type)
return View::make($type);
}

Where you have your {???} this can simply be a regular expression.
Maybe something like this {(archive|browse|masters)}
Update: I think the above works in L3, but L4 has to be done differently
Route::get('/{variable}', function()
{
return View::make('view');
})->where('masters', 'browse', 'archive');

Related

Disabling the calling of the two routes

For my project, I need to have dynamic routes, because {slug} in URL can point to multiple resources.
/shoes - poinst to category
/black-slippers - points to product
Beside the wildcard route, I have also a few (50) static routes (all defined before wildcard route in routes/web.php)
But now, when is called static route, the wildcard route is performed also, e.g.:
Route::get('/profile', [\App\Http\Controllers\Frontend\UserProfileController::class, 'show'])->name('profile.show');
Route::get('{address}', [\App\Http\Controllers\Core\WebaddressController::class, 'resolveAddress'])->where('address', '.*');
In the browser is displayed Profile page (correctly), but in SQL Queries I see, that the query which is called in WebaddressController#resolveAddress is performed also.
If I comment wildcard Route, the query disappears.
What can I do to not perform wildcard route? Thanks
Please do not suggest changing the route style, I cant, this is the requested form.
You can exclude some keywords from the wildcard route with regex in the where statement:
Route::get(
'{address}',
[\App\Http\Controllers\Core\WebaddressController::class, 'resolveAddress']
)
->where('address', '^(?!profile|other-static-route)$');
The list of keywords doesn't have to be hardcoded. You could create a list yourself, or parse keywords from the routes you defined, like this:
use Illuminate\Support\Str;
$keywords = collect(Route::getRoutes())
->map(function ($route) {
return Str::afterLast($route->uri(), '/');
})
->filter(function ($keyword) {
return !Str::endsWith($keyword, '}');
})
->implode('|');
Add them to the where statement like this:
->where('address', '^(?!' . $keywords . ')$');
I am not sure is that a best practice, but you can make a custom middleware for:
Route::get('{address}', [\App\Http\Controllers\Core\WebaddressController::class, 'resolveAddress'])->where('address', '.*')
->middleware('is_slug_route');
And in your handle method of freshly created middleware you can check is provided url an actual slug.
public function handle($request, Closure $next) {
$possibleSlug = $request->getPathInfo();
if (Address::where('slug',$possibleSlug)->exists()) {
return $next($request);
}
}
Something like that

Laravel: Best way to implement dynamic routing in routes.php based on environment variable?

My aim is to roll out a big re-theming / re-skinning (including new URL routing) for a Laravel v5 project without touching the existing business logic (as much as possible that is).
This is my current approach:
I placed a APP_SKIN=v2 entry in my .env file
My app\Http\routes.php file has been changed as follows:
if (env('APP_SKIN') === "v2") {
# Point to the v2 controllers
Route::get('/', 'v2\GeneralController#home' );
... all other v2 controllers here ...
} else {
# Point to the original controllers
Route::get('/', 'GeneralController#home' );
... all other controllers
}
All v2 controllers have been placed in app/Http/Controllers/v2 and namespaced accordingly
All v2 blade templates have been placed in resources/views/v2
the rest of the business logic remains exactly the same and shared between the "skins".
My question: Is there a "better" way to achieve the above?. Please note that the idea here is to affect as few files as possible when doing the migration, as well as ensure that the admin can simply change an environment variable and "roll back" to the previous skin if there are problems.
Within app/Providers/RouteServiceProvider.php you can define your routes and namespaces, etc. This is where I would put the logic you talked about (rather than in the routes file):
protected function mapWebRoutes()
{
if (App::env('APP_SKIN') === 'v2') {
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
require base_path('routes/web_v2.php');
});
} else {
// ...
}
}
This way, you can create separate route files to make it a bit cleaner.
Aside from that, I personally can't see a better solution for your situation than what you described as I'm guessing your templates want to vary in the data that they provide, which if that is the case then you will need new controllers - otherwise you could set a variable in a middleware which is then retrieved by your current controllers which could then determine which views, css and js are included. This would mean you would only need to update your existing controllers, but depending upon your current code - this could mean doing just as much work as your current solution.
Routes pass through Middleware. Thus you can achieve this by BeforeMiddleware as follows
public function handle($request, Closure $next)
{
// Get path and append v2 if env is v2
$path = $request->path();
$page = $str = str_replace('', '', $path); // You can replace if neccesary
// Before middleware
if (env('APP_SKIN') === "v2")
{
return $next($request);
}
else
{
}
}

Get last part of current URL in Laravel 5 using Blade

How to get the last part of the current URL without the / sign, dynamically?
For example:
In www.news.com/foo/bar get bar.
In www.news.com/foo/bar/fun get fun.
Where to put the function or how to implement this in the current view?
Of course there is always the Laravel way:
request()->segment(count(request()->segments()))
You can use Laravel's helper function last. Like so:
last(request()->segments())
This is how I did it:
{{ collect(request()->segments())->last() }}
Use basename() along with Request::path().
basename(request()->path())
You should be able to call that from anywhere in your code since request() is a global helper function in Laravel and basename() is a standard PHP function which is also available globally.
The Route object is the source of the information you want. There are a few ways that you can get the information and most of them involve passing something to your view. I strongly suggest not doing the work within the blade as this is what controller actions are for.
Passing a value to the blade
The easiest way is to make the last part of the route a parameter and pass that value to the view.
// app/Http/routes.php
Route::get('/test/{uri_tail}', function ($uri_tail) {
return view('example')->with('uri_tail', $uri_tail);
});
// resources/views/example.blade.php
The last part of the route URI is <b>{{ $uri_tail }}</b>.
Avoiding route parameters requires a little more work.
// app/Http/routes.php
Route::get('/test/uri-tail', function (Illuminate\Http\Request $request) {
$route = $request->route();
$uri_path = $route->getPath();
$uri_parts = explode('/', $uri_path);
$uri_tail = end($uri_parts);
return view('example2')->with('uri_tail', $uri_tail);
});
// resources/views/example2.blade.php
The last part of the route URI is <b>{{ $uri_tail }}</b>.
Doing it all in the blade using the request helper.
// app/Http/routes.php
Route::get('/test/uri-tail', function () {
return view('example3');
});
// resources/views/example3.blade.php
The last part of the route URI is <b>{{ array_slice(explode('/', request()->route()->getPath()), -1, 1) }}</b>.
Try request()->segment($number) it should give you a segment of the URL.
In your example, it should probably be request()->segment(2) or request()->segment(3) based on the number of segments the URL has.
YourControllor:
use Illuminate\Support\Facades\URL;
file.blade.php:
echo basename(URL::current());
It was useful for me:
request()->path()
from www.test.site/news
get -> news
I just had the same question. In the meantime Laravel 8. I have summarised all the possibilities I know.
You can test it in your web route:
http(s)://127.0.0.1:8000/bar/foo || baz
http(s)://127.0.0.1:8000/bar/bar1/foo || baz
Route::get('/foo/{lastPart}', function(\Illuminate\Http\Request $request, $lastPart) {
dd(
[
'q' => request()->segment(count(request()->segments())),
'b' => collect(request()->segments())->last(),
'c' => basename(request()->path()),
'd' => substr( strrchr(request()->path(), '/'), 1),
'e' => $lastPart,
]
)->where('lastPart', 'foo,baz'); // the condition is only to limit
I prefer the variant e).
As #Qevo had already written in his answer. You simply make the last part part of the request. To narrow it down you can put the WHERE condition at the route.
Try with:
{{ array_pop(explode('/',$_SERVER['REQUEST_URI'])) }}
It should work well.

Share variable based on route group

I have 2 versions of a site. One is located in the root URL of the site and one is using a route prefix. They use the same resources but provide different links when accessed from the prefixed route:
Route::get('/', function(){
View::share('outgoing_url','something.com');
//regular links here
});
and a few more of the above pointing to different routes or
Route::group(array('prefix'=>'tour'), function(){
View::share('outgoing_url','somethingelse.com');
//different links here
});
View::share doesn't work since it uses whatever is assigned last so I am trying to find a solution for this problem.
Also, when I use HTML::link() in the views that go through the prefix, everything still points to the root URI of the site instead of the 'tour' prefix. Is there any way to differentiate between the two? Right now I am stuck with this problem and the only solution seems to be to make identical copies of the views and controllers responding to the routes. But that approach seems stupid to say the least.
I hope I explained the problem understandably.
HTML Macro:
<?php
HTML::macro('myLink', function($url, $title = null, $attributes = array(), $secure = null)
{
if (Request::segment(1) === 'tour')
{
$url = 'tour/'.$url;
}
return HTML::link($url, $title, $attributes, $secure);
});
?>
Usage:
HTML::myLink(...);
Just use a before filter - and set it that way
App::before(function($request)
{
if (Request::segment(1) === 'tour')
{
View::share('outgoing_url','tour.com');
}
else
{
View::share('outgoing_url','other.com');
}
});

Codeigniter - url segment replace or redirect

I have a base controller (base) which all other controllers extend from.
Anything placed here will override other controllers, the redirects will be here.
URLs example:
http://domain.com/controllerone/function
http://domain.com/controllertwo/function
http://domain.com/controllerthree/function
Using the code below. will give me the controller name
$this->uri->segment(1);
Each of the above controllers need to be redirected to separate URLs, but the funcation part should not change:
http://domain.com/newcontrollerone/function
http://domain.com/newcontrollertwo/function
http://domain.com/newcontrollerthree/function
In my base controller i want the following logic:
$controller_name = $this->uri->segment(1);
if($controller_name === 'controllerone'){
// replace the controller name with new one and redirect, how ?
}else if($controller_name === 'controllertwo'){
// replace the controller name with new one and redirect, how ?
}else{
// continue as normal
}
i was thinking i should use redirect() function and str_replace(), but dont know how efficient these would be. Ideally i do not want to use the Routing class.
thanks.
try
header("Location:".base_url("newcontroller/".$this->uri->segment(2)));
Simple Solution using segment_array:
$segs = $this->uri->segment_array();
if($segs[1] === 'controllerone'){
$segs[1] = "newcontroller";
redirect($segs);
}else if($segs[1] === 'controllertwo'){
$segs[1] = "newcontroller2";
redirect($segs);
}else{
// continue as normal
}
CodeIgniter's URI Routing, should be able to help in this case. However, if you have a good reason not to use it, then this solution may help.
The potential redirects are in an array, where the key is the controller name being looked for in the URL and the value is the name of the controller to redirect to. This may not be the most efficient but I think it should be easier to manage and read than a potentially very long if-then-else statement.
//Get the controller name from the URL
$controller_name = $this->uri->segment(1);
//Alternative: $controller_name = $this->router->fetch_class();
//List of redirects
$redirects = array(
"controllerone" => "newcontrollerone",
"controllertwo" => "newcontrollertwo",
//...add more redirects here
);
//If a redirect exists for the controller
if (array_key_exists($controller_name, $redirects))
{
//Controller to redirect to
$redirect_controller = $redirects[$controller_name];
//Create string to pass to redirect
$redirect_segments = '/'
. $redirect_controller
. substr($this->uri->uri_string(), strlen($controller_name)); //Function, parameters etc. to append (removes the original controller name)
redirect($redirect_segments, 'refresh');
}
else
{
//Do what you want...
}

Resources