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
Related
In my web.php I have the following:
Route::resource('transactions/debit', 'TransactionController', [
'except' => ['show', 'destroy'],
'names' => [
'index' => 'transactions.debit.index',
'create' => 'transactions.debit.create',
'store' => 'transactions.debit.store',
'edit' => 'transactions.debit.edit',
'update' => 'transactions.debit.update',
],
]);
I tried the Laravel reference, but the only thing it says is that $options is an array.
Where can I find what are all the $options that the resource method and others support?
In cases like this where you need more information over what is available in the documentation, generally the next place to look is the code.
In this case, the class that handles resource routes is ResourceRegistrar. You can view the code for the class here.
For example, you can see the following usages in the class:
$ ag "options\['.*?'\]"
vendor/laravel/framework/src/Illuminate/Routing/ResourceRegistrar.php
75: if (isset($options['parameters']) && ! isset($this->parameters)) {
76: $this->parameters = $options['parameters'];
157: if (isset($options['only'])) {
158: $methods = array_intersect($methods, (array) $options['only']);
161: if (isset($options['except'])) {
162: $methods = array_diff($methods, (array) $options['except']);
366: if (isset($options['middleware'])) {
367: $action['middleware'] = $options['middleware'];
388: if (isset($options['names'])) {
389: if (is_string($options['names'])) {
390: $name = $options['names'];
391: } elseif (isset($options['names'][$method])) {
392: return $options['names'][$method];
399: $prefix = isset($options['as']) ? $options['as'].'.' : '';
You can also see that the the options are passed to each individual route in the resource as well.
I'm trying to make simple unique slugs. The slugs are saved correctly in database, so the function is working. I have problems with making them unique.
I have this rule in TagCreateRequest.php
public function rules()
{
$rules = [
'tag' => 'required|min:3',
'tag_slug' => 'required|alpha_dash|unique:tag,tag_slug,'
];
$rule = 'unique:tag';
$segments = $this->segments();
$id = intval(end($segments));
if ($id != 0) {
$rule .= ',tag_slug,' . $id;
}
$rules['tag_slug'][] = $rule;
return $rules;
}
and this in my store function in the controller
public function store(TagCreateRequest $request)
{
$tag = new Tag();
foreach (array_keys($this->fields) as $field) {
$tag->$field = $request->get($field);
}
$tag->save();
return redirect()->route('tags');
}
The error is about trying to add duplicate value
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'unique slug' for key 'tags_tag_unique'
Can someone help me to fix this issue?
You can access the id field magically. ID must be the same route parameter used in your route.
If you use id parameter like as Route::put('post/{id}/update') then you can magically access the id parameter inside your form request. Otherwise, if you call the parameter of {post} Route::put('post/{post}/update'), in your form request you must be call $this->post instead of $this->id, ok?
Please try it:
public function rules()
{
$rules = [
'tag' => 'required|min:3'
];
$slugRule = 'required|alpha_dash|unique:tag_slug';
if (! empty($this->id)) {
$slugRule = 'required|alpha_dash|unique:tag_slug,'.$this->id;
}
$rules['tag_slug'] = $slugRule;
return $rules;
}
This FormRequest will work fine on the store() and update() methods if you inject him in both methods.
See it:
// Your store route
Route::post('/post/store', ['as' => 'post.store', 'uses' => 'YourController#store']);
// YourController store method
public function store(NameSpaced\FormRequest $request)
{
// ...
}
// Your update route
Route::post('/post/{id}/update', ['as' => 'post.update', 'uses' => 'YourController#store']);
// YourController update method
public function update(NameSpaced\FormRequest $request)
{
// ...
}
$rules = [
'tag' => 'required|min:3',
'tag_slug' => 'required|alpha_dash|unique:[table name],[column name]'
];
Try this the first is table name and the second is column name that you wanted to unique, write without adding square braces. or you just pass table name like this,
$rules = [
'tag' => 'required|min:3',
'tag_slug' => 'required|alpha_dash|unique:[table name]'
];
laravel auto checks for the column.
I hope it helps.
I would suggest that you automatically generate a new slug whenever you are creating a tag. I got myself in same issues that you have listed here, so i decided on automatically generating whenever i am creating a new item. I used laravel-sluggable. It automatically generates unique slugs.
As per your question, i have defined a unique slug rule in one of my demo apps like this:
public function rules()
{
return [
'name' => 'required|string|max:255',
'slug' => 'required|string|max:255|unique:categories,slug,'.$this->segment(3),
];
}
Please note that $this->segment(3) refers to the id of the model being updated in the backend pages, it can be different in your application.
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
})
);
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);
}
I'm searching for a cleaner way to validate tags when storing a Post.
All of the input validation takes place within my custom request StorePostRequest. The problem is that I need to check whether the given tags exist in the database, only existing tags are allowed. The function $request->input('tags') returns a string with comma seperated values, for example: Tag1,Tag2,Tag3.
Here is the code:
/**
* Store a newly created resource in storage.
*
* #param StorePostRequest $request
* #return Response
*/
public function store(StorePostRequest $request)
{
//THIS PIECE OF CODE
$tags = explode(',', $request->input('tags'));
$tags = Tag::whereIn('title', $tags)->lists('id');
if(count($tags) < 1)
{
return redirect()->back()->withInput()->withErrors([ trans('tags.min') ]);
}
else if(count($tags) > 5)
{
return redirect()->back()->withInput()->withErrors([ trans('tags.max') ]);
}
//TILL HERE
$post = $request->user()->posts()->create([
'slug' => unique_slug('Post', $request->input('title')),
'title' => $request->input('title'),
'description' => $request->input('description'),
'summary' => $request->input('summary'),
]);
$post->tags()->attach($tags);
return redirect(route('theme.post.show', [$theme->slug, $post->slug]))->with(['success', trans('messages.post.store')]);
}
The code is a little sloppy and redundant when using it in multiple controllers.
In order to solve this, I've created a ValidationServiceProvider to extend the core validator rules. Something like this:
$this->app['validator']->extend('tags', function ($attribute, $value, $parameters)
{
$tags = explode(',', $value);
$tags = Tag::whereIn('title', $tags)->lists('id');
if(count($tags) < 1 || count($tags) > 5))
{
return false;
}
});
Pretty neat. The thing is I still need to be able to access the $tags variable within the controller (because of ->attach($tags)).
Is there a better way of tackling this problem? Or should I stop thinking and just use (and repeat) the code I have?
Thanks in advance, hope it makes some sence.
I am assuming that you understand the use of this class because I have seen that you have defined StorePostRequest class. So, just for clarify, the rules method could looks like:
public function rules()
{
return [
'tags' => ['required', 'tags'] //kb
];
}
Finally, with all the tools in correct place, you only make manipulate the data in your controllers like this:
public function store(StorePostRequest $request)
{
// at this point, the tags are already validated, so we, proceed get them:
$tags = explode(',', $$request->get('tags'));
$post = $request->user()->posts()->create([
'slug' => unique_slug('Post', $request->input('title')),
'title' => $request->input('title'),
'description' => $request->input('description'),
'summary' => $request->input('summary'),
]);
$post->tags()->attach($tags);
return redirect(route('theme.post.show', [$theme->slug, $post->slug]))->with(['success', trans('messages.post.store')]);
}
Keep in mind that inyecting StorePostRequeststore in the controller's function it is already validating and running the rules.
That is enough if you really has defined the StorePostRequest's rules correctly.
foreach($request->tags as $k=>$tags){
$this->validate($request, [
'tags.'.$k => 'required|string|max:20'
]);
}