I have a few routes that takes a couple of UUIDs as parameters:
Route::get('/foo/{uuid1}/{uuid2}', 'Controller#action');
I want to be able to verify that those parameters are the correct format before passing control off to the action:
Route::pattern('uuid1', '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$');
This works fine. However, I really don't want to repeat that pattern so many times (in the real case I have it repeated 8 times for 8 different UUID route parameters).
I can't do this:
Route::get('/foo/{uuid}/{uuid}', 'Controller#action');
Because that produces an error:
Route pattern "/foo/{uuid}/{uuid}" cannot reference variable name "uuid" more than once.
I can lump them all into a single function call since I discovered Route::patterns:
Route::patterns([
'uuid1' => '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$',
'uuid2' => '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$',
]);
But that is still repetitious. Is there a way I can bind multiple pattern keys to a single regular expression?
Ideally I'd like to find a way that avoids something like this:
$pattern = 'uuid regex';
Route::patterns([
'uuid1' => $pattern,
'uuid2' => $pattern,
]);
There's no built in way to handle this, and I actually think the solution you found is pretty nice. Maybe a bit more elegant would be this:
Route::patterns(array_fill_keys(['uuid1', 'uuid2'], '/uuid regex/'));
Related
I am pretty sure this challenge has been solved by someone already but even searching with different words, I could not find a solution for this problem:
I try to give users the possibility to run certain functions of a class based on an argument like
service_class::do_this( "selection-argument" );
but the user shall be able to use "clear words" as well as "aliases" and even "well known" abbreviations or synonyms.
I use switch-case construction to call the "real" function.
Example: To get the contens of a folder, The user can use "getdir", "dir", "Directory", "getfolder", "getcontent", "content", "d-cont" and a number of more other "matching words" to start the function(s) underlaying and getting back the very same result.
Capture-ing lowercase/uppercase is simple. What I search for is an efficient way to capture all possible "variations" - that are, of course different number of variations for different functions called.
At the moment I use multiple "case "": lines after each other, but that makes the code quite long, and further I would like the user to be able to "enahnce" the recognition set for a certain function.
That's why I thought about "stripos" to determine first what "internal word" to use and only then run into the switch-case construction.
Anyone had that issue and can direct me to a "good and efficient" solution?
Seems that Stck-exchange itself had a similar challenge (https://codereview.stackexchange.com/tags/php/synonyms) ... maybe I can simply re-use the underlying code?
Thanks in advance and sorry if I overlooked a solution already posted.
You could use a database or array. Let's do the latter. So to determine whether an user wants to get a directory you would define an array like this:
$getDirVariants = ['getdir',
'dir',
'directory',
'getfolder',
'getcontent',
'content',
'd-cont'];
It is easy to add more of these arrays. To test the query word you would do:
$queryWord = strtolower($queryWord);
if (in_array($queryWord, $getDirVariants)) service_class::getDir(<arguments>);
elseif (in_array($queryWord, $deleteVariants)) service_class::delete(<arguments>);
You can easily add to the arrays or make it a 2D array to contain more commands. That array could also be placed in a database.
Especially when there are many commands, with many variants, a database will be the better solution, because you can find the query word with one database query.
There's a variation I can think of that will also simplify the code when there are many commands. You could use an associative array to find the command:
$commandVariants = ['getdir' => 'getdir',
'dir' => 'getdir',
'directory' => 'getdir',
'getfolder' => 'getdir',
'getcontent' => 'getdir',
'content' => 'getdir',
'd-cont' => 'getdir',
'delete' => 'delete',
'del' => 'delete',
'remove' => 'delete',
'unlink' => 'delete'];
$queryWord = strtolower($queryWord);
if (isset($commandVariants[$queryWord])) {
$command = $commandVariants[$queryWord];
service_class::$command(<arguments>);
}
else echo "I don't recognize that command.";
This uses a variable identifier.
I know Laravel5 Resource method will work like this.
TestControler#index /aa
TestControler#edit /aa/{aa}/edit
..
It's good to work if integer have been inserted.
/aa/1/edit -> work
But it will broken if string is coming.
/aa/aa/edit -> SQLSTATE[22P02]: Invalid text representation ..
So I wanna ask you the question is how should I allow request url thats integer only?
where should I write, route.php or Controller?
and how to abort 404 if string is coming.
any idea?
Expanding on my comment:
When working with Laravel's router, for any parameter you add to a URI definition (such as {id}), you can add a regex constraint. The constraint will take the variable value and test to see if the regex matches the value. If the regex fails, then the route will not be selected.
You do this using the where() method on the route and passing an associative array where the keys correspond to the variables in the URI, and the values are regexes to match. You can add constraints to as many variables in a route's URI as you like.
For example, if you wanted to constrain the id value in your URI to just numbers, you could do something like this:
Route::get("users/{id}", "Users#getUser")->where(["id" => "[0-9]+"]);
The documentation for this feature states:
You may constrain the format of your route parameters using the where method on a route instance. The where method accepts the name of the parameter and a regular expression defining how the parameter should be constrained
See more examples in the documentation available here: https://laravel.com/docs/5.2/routing#parameters-regular-expression-constraints
Thanks to reply, Finally It works great.
But I wanna add this to my post.
Where method will work when I write 'standard' routing like this.
Route::get('/aa/{aa}/edit','TestsController#delete')->name('aa.edit')->where('aa','[0-9]+'); // works great!
But that's not work if I write 'RESTful' routing like this.
Route::resource('/aa', 'TestsController')->where('aa','[0-9]+'); // not work!
So I wrote this to app/route.php, It works very fine.
Route::pattern('aa', '\d+');
Route::get('/aa/{aa}/delete','TestsController#delete')->name('aa.delete')->where('aa','[0-9]+');
Route::resource('/aa', 'TestsController')->where('aa','[0-9]+');
I'm building a small restful api and I'm asking if it's possible to seperate the url to php file and the end of the url.
E.g. www.mydomain.com/api/parameter/1/2/
In this case the php file is adressed with www.mydomain.com/api/ or www.mydomain.com/api/index.php and parameter/1/2/ is the parameter.
I want a CRUD interface so that GET without parameter gets a list of all data. To achieve this I need to check if a parameter is attached and to extract the parameter.
Other example
www.mydomain.com/topics/ => gets all topics
www.mydomain.com/topics/1/posts/ => gets all posts of topic 1,
www.mydomain.com/topics/1/posts/2/ => gets post 2 of topic 1
My question is: Is it possible and how?
You would probably have to read the request URI from the end of the URL using $_SERVER['request_uri']. This would return /api/parameter/1/2. You could then substring it if the length is reliable, or use a regex with preg_match to get just the parameter section. e.g.
preg_match("parameter\/.*", $_SERVER['request_uri'], $matches)
would return either the string parameter/1/2 in the $matches variable, or false if no match was found
But yeah like others are saying, you're probably better using GET parameters if you can, and just do a check using isset() to see if there are any parameters.
im stuck on a piece of code which is looking terrible.
i got an route as string like /people/12/edit and i want to compare it with routes in my dataset.
in the dataset there are routes like:
/people
/people/:id
/people/new
/people/:id/edit
i need to know, that my route /people/12/edit goes to the internal action for /people/:id/edit
so i had the following condition to check this:
if(preg_match("/^".preg_replace('/:id/','([0-9]*)?',preg_replace('/\//','\/',$_route['route']))."$/", $route)){
// ...
}
but it seems to be a bad solution. i have to escape the slashes, i have to replace the :id parameter, and after this, i can check if the route is matching.
but it looks terrible and has a big problem. it doenst work if the parameter is not named :id.
can you give me some hint or show a better way?
thanks in advance
update:
im not using any mvc framework. its an "build your own framework and learn task"
the routes stored on a route table:
people_index get /people people#index
people_show get /people/:id people#show
people_edit get /people/:id/edit people#edit
people_update put /people/:id people#update
people_new get /people/new people#new
people_create post /people people#create
people_delete delete /people/:id people#delete
if i call link_to 'people_index' it will display /people. the condition above is part of the routing parser. it just looking for the correct uri and return (for the edit link) people#edit. after this, i know there is an resource PeoplesController and call the edit action.
i know there are lots of great mvc frameworks for php. but i want to get more experience and rebuild some rails logic into php :)
first of all I would split this up some, preferable in a reusable way, say a function to parse the mapping first.
function createpattern( $map ) {
$map = preg_replace('/:id/', '([0-9]+|new)', $map );
//add other mappings here for example.
$map = preg_replace('/:name/', '([a-z])', $map );
return $map;
}
Or just use rusty trusty str_replace for this part.
function createpattern( $map ) {
$search = array( ':id', ':name' );
$replace = array( '([0-9]+|new)', '([a-z]+)?');
$map = preg_quote($map); //maybe use it here.
$map = str_replace($search, $replace, $map );
return $map;
}
So this
/people/:id
becomes
/people/([0-9]+|new)
Somewhere you should use preg_quote, but not after adding in the regx bits.
essentially your regx is now
\/people\/([0-9]+|new)\/... etc.
preg_quote would be used to prevent a user from adding in regular expressions of there own and messing up yours, unintentional and otherwise.
I'm using PHP and ToroPHP for routing.
Unknown number of child pages
It works fine, but in my case I can add pages with childs and parents, where a parent can have an unknown number of have childpages.
In ToroPHP it might look like this:
// My Path: http://www.test.com/product/house/room/table/leg/color/
Toro::serve(array(
"/" => "Home",
"/:string/:string/:string/:string/:string/:string/" => "Page"
));
class Page {
function get($slug, $slug2, $slug3, $slug4, $slug5, $slug6) {
echo "First slug: $slug";
}
}
Problem
I can figure out what the maximum depth can be and then loop and append
out a string containing the "/:string" parameters but it don't look
that nice.
The get-function in the Page-class takes an unknown number of in
parameters. I can calculate the max depth from outside the function, but I need the function to know how many values to take.
Question
Is there an alternative way the the problem 1? Some regular expression maybe?
How can I make a function take an unkown number of in parameters?
Maybe I try to solve this the wrong way and the first two questions are not relevant? Correct me if that is.
In order for your action to receive all parameters, you need to capture them in your regex. You capture a value in regular expressions using parentheses. :string is just an alias for ([a-zA-Z]+). You could apply a wildcard after the first segment, like this:
"/product/(.*?)" => "Page"
However, this means that you need to parse the URL by yourself in your action, which is not very clean either.
If you want to make this particular case more clean, an option would be to use str_repeat:
Toro::serve(array(
"/" => "Home",
"/" . str_repeat(":string/", 6) => "Page"
));
ToroPHP is a very simple library, it should not be that hard to fork it and bend it to your will. Ideally, how would you like to define routes like this? Maybe a route like /:string*6?
You can always pass more or fewer parameters than defined to a PHP function. Use func_get_args to get all passed parameters and func_num_args to get the number of passed parameters.
In response to question 2, can you possibly format the GET parameters into an array and then pass in arrays rather than individual values?
Maybe like:
$allSlugs = array($slug, $slug2, $slug3, $slug4, $slug5, $slug6);
// Pass $allSlugs into your instance of Page::get($allSlugs);
class Page {
function get($getValues) {
echo isset($getValues[0]) ? "First slug: ".$getValues[0] : '';
}
}