I am trying to build a simple REST API for a dictionary App. I would like to have two GET methods that will get you the word by id and query(string). The problem is that the Slim framework routes everything through the first method and ignores the second one. I understand why it does it and I know you can use query string params but I am hoping there would be a way I can accomplish it this way. Thank you for your help.
http://localhost:5000/dictionary_api/words/1
$app->get('/words/:id', function($id) use ($app, $db) {
});
http://localhost:5000/dictionary_api/words/hello
$app->get('/words/:word', function($word) use ($app,$db){
});
You can supply an array of conditions (regex matches) so that the route parameter will only match a certain format. Try the following
$app->get('/words/:id', function($id) use ($app, $db)
{
//
})->conditions(['id' => '[0-9]+']);
This will make the :id parameter only match numeric values, anything else it shouldn't match and skip onto the next route.
Related
I am currently using the lumen framework (5.6) to build an API, this API can be used to request a page by for example its title. The route for this is:
Route::group(["prefix" => '/api/v1', "middleware" => ["ContentTypeJson","Paginator"]], function () {
Route::group(["prefix" => '/{databaseIdentifier}', "middleware"=>"DatabaseIdentifier"], function () {
Route::group(["prefix" => '/pages'], function () {
Route::group(["prefix" => '/{title}'], function () {
Route::get("/", "PageController#getPageByTitle");
Route::get("/parents", "SearchController#getParentalSpecies");
Route::get("/all", "PageController#getPageByTitleWithLinks");
Route::get("/overlap/{overlapProperty}", "PageController#getPagesWithOverlap");
Route::put("/", "PageController#overwritePage");
});
});
});
As you can see the title is used in multiple functions and controllers, the same applies to the databaseIdentifier which is used in the middleware to determine which database needs to be used.
However all url parameters with a space will be converted with %20 instead of a space, which is the expected behaviour. However I would like to convert this back to the raw string, which can be done with urldecode().
But since this is applied in every controller and function I would like to use some kind of preprocessing step for this.
I have tried using a middleware for this to alter the route parameters as suggested here (using $request->route()->setParameter('key', $value);).
Unfortunately this does not work in lumen since the result of $request->route() is an array and not an object. I have tried altering this array but I can not get it to change the actual array in the Request object. No error appears here.
So in short: I am looking for a way to urldecode every URL parameter which is passed to my controllers and functions without putting $param = urldecode($param); everywhere.
If you need more information feel free to ask
Thank you in advance
For anyone who also encounters this issue I have found a solution using middleware.
In the middleware I do the following:
public function handle(Request $request, Closure $next)
{
$routeParameters = $request->route(null)[2];
foreach ($routeParameters as $key=>$routeParameter) {
$routeParameters[$key] = urldecode($routeParameter);
}
$routeArray = $request->route();
$routeArray[2] = $routeParameters;
$request->setRouteResolver(function() use ($routeArray)
{
return $routeArray;
});
return $next($request);
}
This code will decode every route parameter and save it in an array, then I take the whole route array which is created by lumen itself (which contains the url encoded parameters), these are then replaced with the url decoded version of the parameter. This is not enough because this does not affect the route array in the Request object.
In order to apply these changes I alter the routeResolver so it will return the changed array instead of the one created by lumen.
I have 2 kind on route.
like this:
You know for these:
Route::get('/{category_slug}/{article_slug}', 'mController#list');
Route::get('/{category_slug}/{subcategory_slug?}', 'mController#clist');
It run only first route.
I try binding them in RouteServiceProvider boot(){
Route::bind('category_slug', function ($category_slug, $route) {dd('category_slug') }); //works
Route::bind('article_slug', function ($article_slug, $route) { dd('article_slug') }); //works for both article_slug(ok!) and subcategory_slug(wrong!)
Route::bind('subcategory_slug', function ($subcategory_slug, $route) { dd('subcategory_slug') }); //not works
}
Is there a way to check {article_slug} or {subcategory_slug} before loading route and then system choosing right route? for example if first is wrong, then skip it and try to run second route.
for example middleware can do that?
You need to use different url or different method of request in order to fulfil your demand.
Like -
Route::get('/{category_slug}/{article_slug}', 'mController#list');
Route::post('/{category_slug}/{subcategory_slug?}', 'mController#clist');
Use Route::update() if need to modify a data which is already in database or Request::delete() to delete a data in the database.
Or like the answer of tprj29 you need to use different kinds of url
In Laravel slugs are unique per table not unique throughout all tables. I would recommend using something like a keyword to determine what slug is expected. This is way more efficient and cleaner then querying to determine if the slug is of an type article or category of object.
Route::get('/{category_slug}/', 'mController#clist');
Route::get('/{category_slug}/sub/{subcategory_slug}', 'mController#clist');
Route::get('/{category_slug}/sub/{subcategory_slug}/{article_slug}', 'mController#list');
Route::get('/{category_slug}/{article_slug}', 'mController#list');
I am creating the API using slim framework. I faced the following problem.
I use one of the routes for given input.That is, json input: { "tagname": "tname"}. Route is
$app->post('/tag',function () use($app, $db){
//code
});
Now, I want to use the same route for another input.json: [{"tid": "1"},{"tid": "2"}]. Route is
$app->post('/tag',function () use($app, $db){
//code
});
How do solve it?
Slim's router can't call different functions for same path based on received content.
In your particular case the simplest way to deal with two different types of input data on one route would be something like this (I assume you are getting data as POST body with application/json which is not processed by Slim2)
$app->post('/tag',function () use($app, $db){
$payload = json_decode(file_get_contents('php://input'));
if(is_array($payload)) {
// code to deal with [{"tid": "1"},{"tid": "2"}]
} else {
// code to deal with { "tagname": "tname"}
}
});
But even easier and logically would be make /tag route for single and /tags for multiple. Or just require to send all tags as array - even single one.
you can pass extra parameter to perform another action in same route and separate your code with if condition
I've built my first RESTful API ever and used Slim as my framework. It works well so far.
Now I have seen a great API Design Guide which explained, the best way to build an API is to keep the levels flat. I want to do that and try to figure out how to build an URI like this:
my-domain.int/groups/search?q=my_query
The /groups part already works with GET, POST, PUT, DELETE and also the search query works like this:
my-domain.int/groups/search/my_query
This is the code I use for the routing in PHP:
$app->get('/groups/search/:query', 'findByName');
I just can't figure out how to build optional parameters with an question mark in Slim. I wasn't able to find anything on Google.
EDIT:
Since the search not seems to be suitable for my scenario I try to show another way of what I want to realize:
Let's say I want to get a partial response from the API. The request should look like that:
my-domain.int/groups?fields=name,description
Not like that:
my-domain.int/groups/fields/name/description
How do I realize that in the routing?
The parameters supplied with the query string, the GET parameters, don't have to be specified in the route parameter. The framework will try to match the URI without those values. To access the GET parameters you can use the standard php approach, which is using the superglobal $_GET:
$app->get('/groups/test/', function() use ($app) {
if (isset($_GET['fields']){
$test = $_GET('fields');
echo "This is a GET route with $test";
}
});
Or you can use the framework's approach, as #Raphael mentioned in his answer:
$app->get('/groups/test/', function() use ($app) {
$test = $app->request()->get('fields');
echo "This is a GET route with $test";
});
Ok I found an example that does what I need on http://help.slimframework.com/discussions/problems/844-instead
If you want to construct an URI Style like
home.int/groups/test?fields=name,description
you need to build a rout like this
$app->get('/groups/test/', function() use ($app) {
$test = $app->request()->get('fields');
echo "This is a GET route with $test";
});
It echoes:
This is a GET route with name,description
Even though it's not an array at least I can use the question mark. With Wildcards I have to use /
You may also have optional route parameters. These are ideal for using one route for a blog archive. To declare optional route parameters, specify your route pattern like this:
<?php
$app = new Slim();
$app->get('/archive(/:year(/:month(/:day)))', function ($year = 2010, $month = 12, $day = 05) {
echo sprintf('%s-%s-%s', $year, $month, $day);
});
Each subsequent route segment is optional. This route will accept HTTP requests for:
/archive
/archive/2010
/archive/2010/12
/archive/2010/12/05
If an optional route segment is omitted from the HTTP request, the default values in the callback signature are used instead.
Search query is not suitable for url parameters, as the search string might contain url separator (/ in your case). There's nothing wrong to keep it as query parameter, you don't have to push this concept everywhere.
But to answer your question, optional parameters are solved as another url:
$app->get('/groups/search/:query', 'findByName');
$app->get('/groups/search/strict/:query', 'findByNameStrict');
EDIT: It seems you want to use Slim's wildcard routes. You just need to make sure there's only one interpratation of the route.
$app->get('/groups/fields/:fields+', 'getGroupsFiltered');
Parameter $fields will be an array.
I am using Laravel. I would like users to be able to perform a search on my website using up to 3 criteria. These criteria are: Class, Brand and Model.
They should be free to use any or all of them when searching. As the relationship between these isn't as simple as Many->1, Many->1, Many->1, and also given the criteria will be numbered if blank, I dont want to use pretty urls to post the search criteria as they would look like this:
/SearchResults/0/BMW/0
which is meaningless to users and search engines. I therefore want to use normal dynamic addresses for this route as follows:
/SearchResults/?Class=0&Brand="BMW"&Model=0
How do I define a route that allows me to extract these three criteria and pass it to a custom method in my resource controller?
I have tried this but it isnt working:
Route::get('/SearchResults/?Class={$class}&Brand={$brand}&Model={$type}', 'AdvertController#searchResults');
Many thanks
The Symfony Routing components fetch the REQUEST_URI server variable for matching routes, and thus Laravel's Route Facade would not pick up URL parameters.
Instead, make use of Input::get() to fetch them.
For example, you would start by checking if the class param exists by using Input::has('class'), and then fetching it with Input::get('class'). Once you have all three, or just some of them, you'd start your model/SQL query so that you may return your results to the user.
You will need to route all to the same method and then, within the controller, reroute that given action to the correct method within the controller.
For that, I recommend using the strategy pattern (read more here).
I would do something like this:
route.php
Route::get('/SearchResults', 'AdvertController#searchResults');
AdvertController.php
use Input;
...
private $strategy = [];
public function __construct(){
$strategy = [
/*class => handler*/
'0'=> $this->class0Handler,
'1'=>$this->class1Handler,
...];
}
private function class0Handler(){
//your handler method
}
public function searchResults(){
if( !array_key_exists(Input::get('class'),$this->strategy))
abort(404);
return $this->strategy[Input::get('class')]();
}
In case you are breaking down search by other types, you define the handler in the $strategy variable.
Strategy pattern has a lot of benefits. I would strongly recommend it.