Handle url's character casing in Laravel? - php

How to I redirect following urls to lower case url?
http://domain.com/City/really-long-slug-from-db/photos
http://domain.com/city/Really-Long-Slug-From-Db/photos
http://domain.com/City/really-long-slug-from-db/Photos
to
http://domain.com/city/really-long-slug-from-db/photos
This is my route:
Route::any('/{city}/{slug}/{page?}',
array(
'as' => 'slug-page',
function($city, $slug, $page="info"){
return View::make('default.template.'.$page)
->with('city', $city)
->with('page',$page)
->with('slug', $slug);
}
))
->where(
array(
'city' => '[a-z ]+',
'page' => '[a-z-]+',
'slug' => '(about|photos|videos)'
));
Currently I used regex [a-z-]+ to match only smaller case strings and that throws NotFoundHttpException for obvious reasons.
How do I accept all these parameters in case insensitive strings and 301 redirect(to avoid duplicate urls) to smaller case urls in Laravel 5.1?

You could easily do that with a route middleware. The middleware should check if there are any uppercase characters in the path and redirect to lowercased version.
First, define the middleware class:
class RedirectToLowercase
{
public function handle($request, Closure $next) {
$path = $request->path();
$pathLowercase = strtolower($path); // convert to lowercase
if ($path !== $pathLowercase) {
// redirect if lowercased path differs from original path
return redirect($pathLowercase);
}
return $next($request);
}
}
Then register the new middleware in your Kernel.php:
protected $routeMiddleware = array(
// ... some other middleware classes ...
'lowercase' => 'App\Http\Middleware\RedirectToLowercase'
);
Finally, apply the middleware to your route:
Route::any('/{city}/{slug}/{page?}', array(
'as' => 'slug-page',
'middleware' => 'lowercase',
function() {
// your code
})
);

Related

How to check url on array using regex in php?

Firstly, I tried all the questions & answers related to this topic. Additionally and I tried related questions and try to solve it but no success. So please read my question thoroughly.
How to check url is valid on my Routes using Regex patterns ?
This Code write "Core PHP" not any Framework.
$routes = [
'PUT' =>
[
'/order/{id}' => 'updateOrder'
],
'GET' =>
[
'/order' => 'getOrder',
'/order/{id}' => 'getOrder',
'/order/status/{id}' =>'getOrderStatus'
],
'POST' =>
[
'/order' =>'createOrder'
],
'DELETE' =>
[
'/order/{id}' => 'deleteOrder'
]
];
My url like :
1) '/order/BPQ153'
2) '/order/status/BPQ123'
3) '/order'
You can simply loop through the routes and find the first matching one. Note that the outer loop in below code is only because I am checking all the sample URLs you provided at once:
$routes = [
'PUT' =>
[
'/order/{id}' => 'updateOrder'
],
'GET' =>
[
'/order' => 'getOrder',
'/order/{id}' => 'getOrder',
'/order/status/{id}' => 'getOrderStatus'
],
'POST' =>
[
'/order' => 'createOrder'
],
'DELETE' =>
[
'/order/{id}' => 'deleteOrder'
]
];
$urlsToCheck = [
'/order/BPQ153',
'/order/status/BPQ123',
'/order',
];
foreach ($urlsToCheck as $url) {
foreach ($routes as $method => $methodValues) {
foreach ($methodValues as $route => $function) {
// match ID to everything that is not a slash
$regex = str_replace('{id}', '([^\/]+)', $route);
if (preg_match('#^' . $regex . '$#', $url)) {
echo "The URL $url matches on $method HTTP method for function $function.";
echo PHP_EOL;
}
}
}
}
this outputs:
The URL /order/BPQ153 matches on PUT HTTP method for function updateOrder.
The URL /order/BPQ153 matches on GET HTTP method for function getOrder.
The URL /order/BPQ153 matches on DELETE HTTP method for function deleteOrder.
The URL /order/status/BPQ123 matches on GET HTTP method for function getOrderStatus.
The URL /order matches on GET HTTP method for function getOrder.
The URL /order matches on POST HTTP method for function createOrder.
As you can see, I did not check for a specific HTTP method, but you would have to add an extra check in there depending on the current HTTP method that is used. However that was not part of the question, so I am only mentioning it here for completeness.
P.S.: For cleaner code you can of course put this into a function / method / class, I just tried to keep the code as short as possible here.
First of all you have to define your routes a little more detailed. Otherwise, it is not clear how your placeholders in the curly brackets should be used. Of course you as a programmer know that there should be a numerical value for an ID. Your validator may not know this.
So let us have a look, how you can define your routes a little bit more detailed.
$routes = [
[
'controller' => SomeController::class,
'route' => '/order/{id}',
'parameters' => [ 'id' => '([0-9a-z]+)' ],
'allowed_methods' => [ 'GET' ],
]
];
This is just an example entry for a route. A route contains the controller, which has to be called, when this route is requested. Also the route itself is mentioned here. Beside that we define a parameter called id which acts as a placeholder in your route and we define the allowed request methods. In this case the route should only be accessible via GET requests. In our small example here, we just need the parameters and the route. The below shown router class does not recognize the request method and the controller.
So how a route can be resolved and validated? All we have to know is now defined in the route. When a request happens, we can check against the route.
Here 's a small router class example. Ofcourse this small example should not be used for production.
declare(strict_types=1);
namespace Marcel\Router;
class Router
{
protected array $routes = [];
protected array $filters = [];
public function __construct(array $routes)
{
$this->routes = $routes;
}
public function match(string $request) : bool
{
foreach ($this->routes as $route) {
// find parameters and filters (regex) in route
if (preg_match_all('/\{([\w\-%]+)\}/', $route['route'], $keys)) {
$keys = $keys[1];
}
foreach ($keys as $key => $name) {
if (isset($route['parameters'][$name])) {
$this->filters[$name] = $route['parameters'][$name];
}
}
// match requested route against defined route
$regex = preg_replace_callback('/\{(\w+)\}/', [ $this, 'substituteFilter' ], $route['route']);
$filter = rtrim($regex, '/');
$pattern = '#^' . $filter . '/?$#i';
// if not matching, check the next route
if (!preg_match($pattern, $request, $matches)) {
continue;
}
return true;
}
return false;
}
protected function substituteFilter(array $matches): string
{
if (isset($matches[1], $this->filters[$matches[1]])) {
return $this->filters[$matches[1]];
}
return '([\w\-%]+)';
}
}
This small router class example tests the given urls against the collection of routes. The class pays attention to the placeholders that can be filled with a regular expression. So the class checks every request against the defined regex for the given placeholder.
So let us test this little class against some requests
$router = new Router($routes);
$result = $router->match('/order/BPQ123');
var_dump($result); // true
$result = $router->match('/bla/yadda/blubb');
var_dump($result); // false

Laravel Multi-language Route Prefix

I managed to get some routes working with and without a prefix. Having routes that do not have the prefix work properly is important as it's too much work to go back and change all get/post links to the correct localized ones.
For example with the code below the URL localhost/blog redirects to localhost/en/blog (or any other language stored in session).
However, I noticed that URLs with parameters don't work, so /blog/read/article-name will result in a 404 instead of redirecting to /en/blog/read/article-name.
Routes:
Route::group([
'prefix' => '{locale}',
'middleware' => 'locale'],
function() {
Route::get('blog', 'BlogController#index');
Route::get('blog/read/{article_permalink}', 'BlogController#view');
}
);
Middleware is responsible for the redirects which don't seem to fire at all for some routes as if the route group isn't matching the URL.
public function handle($request, Closure $next)
{
if ($request->method() === 'GET') {
$segment = $request->segment(1);
if (!in_array($segment, config('app.locales'))) {
$segments = $request->segments();
$fallback = session('locale') ?: config('app.fallback_locale');
$segments = array_prepend($segments, $fallback);
return redirect()->to(implode('/', $segments));
}
session(['locale' => $segment]);
app()->setLocale($segment);
}
return $next($request);
}

Laravel Multi-language routes without prefix

Currently, my system is using 2 languages which is English and German. my goal is to browse following routes by switching between the mentioned language -
base-url/contact - for english
base-url/kontakte - for german
Currently, I have required routes file in the resource folder, where I have put the necessary translated words.
resources/lang/en/routes.php
resources/lang/de/routes.php
In web.php I have currently -
Route::get(r('contact'), 'TestController#index')->name('contact');
by r() helper function I am getting the active translated word.
On my user table, I have locale column where I am storing the active language when I am updating the language from user profile -
\Session::put('locale', $request->input('locale'));
I have created a middleware Localization where I have currently -
public function handle($request, Closure $next)
{
if ( \Session::has('locale')) {
\App::setLocale(\Session::get('locale'));
Carbon::setLocale(\Session::get('locale'));
}
return $next($request);
}
Currently, the code is working fine for blade translated word. but the translated routes are not working. whenever I switch and visit any route, it gives me 404 error. but if I restart the server by PHP artisan serve, it works with changed language.
So how fix the issue?
:) Try to put \App::setLocale(\Session::get('locale') in the beginning of the routes file (routes.php, or web.php/api.php)
With PHP 8.1.0, Laravel 9.x
resources/lang/en/routes.php:
return [
'post' => '/post/',
'product' => '/product/',
'contact' => '/contact/',
'aboutUs' => '/about-us/'
];
Create LanguageType enum:
enum LanguageType: string
{
case EN = 'en';
case DE = 'de';
public function label(): string
{
return trans(match ($this) {
self::EN => 'English',
self::DE => 'German',
});
}
}
In web.php:
Route::get('{contactTranslation}', [ContactController::class, 'index']);
Route::get('{aboutUsTranslation}', [AboutUsController::class, 'index']);
Route::get('{productTranslation}', [ProductController::class, 'index']);
Route::get('{postTranslation}', [PostController::class, 'index']);
//...
In RouteServiceProvider.php/boot(), add:
foreach (trans('routes') as $key => $value) {
Route::pattern($key . 'Translation', $this->getTranslation($key));
}
And function:
private function getTranslation($slug): string
{
$slugList = collect();
foreach (LanguageType::cases() as $language) {
$slugList = $slugList->merge(trim(trans('routes.' . $slug, [], $language->value), '/'));
}
return $slugList->implode('|');
}

CakePHP 3: switching between Router scopes

I have a simple routing setup to address different languages. The goal is to have the language as a first parameter in the url like /en/docs or /de/docs. The language definitions are set as scopes as show below:
$builder = function ($routes)
{
$routes->connect('/:controller', ['action' => 'index']);
$routes->connect('/:controller/:action/*', ['action' => 'index']);
};
$languages = ['en', 'de'];
foreach ($languages as $lang) {
Router::scope('/'.$lang, ['language' => $lang], $builder);
}
Router::addUrlFilter(function ($params, $request) {
if ($request->param('language')) {
$params['language'] = $request->param('language');
}
else {
$params['language'] = 'en';
}
return $params;
});
Each of the scopes is working as expected. Even with some more complex connects and prefixes (I removed them from the code above to make the code more readable).
The problem is now: how to create a link to switch between the languages (scopes)?
I tried different urls, but depending on the current url (e.g. /de/docs), the language parameter does not have any effect on the created urls:
Router::url(['language' => 'en', 'controller' => 'docs']);
// -> /de/docs (expected: /en/docs)
Router::url(['language' => 'de', 'controller' => 'docs']);
// -> /de/docs
How to fix the routes to get the expected urls?
I found the reason for this behaviour. The function addUrlFilter replaced the language parameter when generating the urls and the result was always the current language. An updated version of this function does the job:
Router::addUrlFilter(function ($params, $request) {
if (!isset($params['language'])) {
$params['language'] = $request->param('language');
}
return $params;
});
Puuh, one hour of searching and trying...

Pass fixed variable from route to controller in Laravel

I'm trying to pass a variable through my route to my controller, but I have multiple routes (categories) leading to the same controller i.e.
Route::get('/category1/{region}/{suburb?}', 'SearchController#search');
Route::get('/category2/{region}/{suburb?}', 'SearchController#search');
Making /category1, 2, etc. to be a parameter /{category} is not an option and I don't want to make separate controller function for each category.
How do I send the first segment of the url to my search controller? i.e. category1 or category2?
At present controller is as follows:
public function search($region, $suburb = null) { }
Thanks!
You can specify a mask for your {category} parameter so that it must fit the format "category[0-9]+" in order to match the route.
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'category[0-9]+');
Now, your example url (from the comments) www.a.com/var1/var2/var3 will only match the route if var1 matches the given category regex.
More information can be found in the documentation for route parameters here.
Edit
Yes, this can work with an array of string values. It is a regex, so you just need to put your array of string values into that context:
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'hairdresser|cooper|fletcher');
Or, if you have the array built somewhere else:
$arr = ['hairdresser', 'cooper', 'fletcher'];
// run each array entry through preg_quote and then glue
// the resulting array together with pipes
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', implode('|', array_map('preg_quote', $arr)));
Edit 2 (solutions for original request)
Your original question was how to pass the hardcoded category segment into the controller. If, for some reason, you didn't wish to use the solution above, you have two other options.
Option 1: don't pass the value in, just access the segments of the request in the controller.
public function search($region, $suburb = null) {
$category = \Request::segment(1);
dd($category);
}
Option 2: modify the route parameters using a before filter (L4) or before middleware (L5).
Before filters (and middleware) have access to the route object, and can use the methods on the route object to modify the route parameters. These route parameters are eventually passed into the controller action. The route parameters are stored as an associative array, so that needs to be kept in mind when trying to get the order correct.
If using Laravel 4, you'd need a before filter. Define the routes to use the before filter and pass in the hardcoded value to be added onto the parameters.
Route::get('/hairdresser/{region}/{suburb?}', ['before' => 'shiftParameter:hairdresser', 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['before' => 'shiftParameter:cooper', 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['before' => 'shiftParameter:fletcher', 'uses' => 'SearchController#search']);
Route::filter('shiftParameter', function ($route, $request, $value) {
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// union the new parameters and the old parameters
$parameters = ['customParameter0' => $value] + $parameters;
// loop through the new set of parameters to add them to the route
foreach($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
});
If using Laravel 5, you'd need to define a new before middleware. Add the new class to the app/Http/Middleware directory and register it in the $routeMiddleware variable in app/Http/Kernel.php. The logic is basically the same, with an extra hoop to go through in order to pass parameters to the middleware.
// the 'parameters' key is a custom key we're using to pass the data to the middleware
Route::get('/hairdresser/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['hairdresser'], 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['cooper'], 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['fletcher'], 'uses' => 'SearchController#search']);
// middleware class to go in app/Http/Middleware
// generate with "php artisan make:middleware" statement and copy logic below
class ShiftParameterMiddleware {
public function handle($request, Closure $next) {
// get the route from the request
$route = $request->route();
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach ($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// build the new parameters to shift onto the array
// from the data passed to the middleware
$newParameters = [];
foreach ($this->getParameters($request) as $key => $value) {
$newParameters['customParameter' . $key] = $value;
}
// union the new parameters and the old parameters
$parameters = $newParameters + $parameters;
// loop through the new set of parameters to add them to the route
foreach ($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
return $next($request);
}
/**
* Method to get the data from the custom 'parameters' key added
* on the route definition.
*/
protected function getParameters($request) {
$actions = $request->route()->getAction();
return $actions['parameters'];
}
}
Now, with the filter (or middleware) setup and in use, the category will be passed into the controller method as the first parameter.
public function search($category, $region, $suburb = null) {
dd($category);
}

Categories