I am setting up an API with Laravel so that I can connect with an AngularJS front-end.
I have 2 routes that go to the same controller method -> BallController#getSpecs
My routes are set up as follows:
Route::group(['prefix' => '/api/v1', 'before' => 'auth.basic'], function() {
Route::group(['prefix' => '/ball'], function() {
Route::get('/', 'BallController#getIndex');
Route::get('{id}', 'BallController#getIndex');
Route::get('specs', 'BallController#getSpecs');
Route::get('{id}/specs', 'BallController#getSpecs');
});
});
Now I am having trouble with Route::get('specs', 'BallController#getSpecs'); route.
The getSpecs method is defined as follows:
public function getSpecs($id = null)
{
if(empty($id))
{
Ball::all()->each(function($ball) {
$json = [$ball->id => [
'name' => $ball->name,
'core' => $ball->core->toArray(),
'coverstock' => $ball->coverstock->toArray(),
'finish' => $ball->finish->toArray(),
'manufacturer' => $ball->manufacturer->toArray()
]];
});
return Response::json($json);
}
else
{
$ball = Ball::find($id);
if(empty($ball))
{
return Response::json('You\'ve got no ballss', 404);
}
else
{
return Response::json([$ball->id => [
'name' => $ball->name,
'core' => $ball->core->toArray(),
'coverstock' => $ball->coverstock->toArray(),
'finish' => $ball->finish->toArray(),
'manufacturer' => $ball->manufacturer->toArray()
]]);
}
}
}
When I call /api/v1/ball/1/specs specifying an id I get the correct information back, however when I call /api/v1/ball/specs my function returns my error message 'You've got no balls'
ID should be null in this cast putting me into the first part of my if statement but for some reason I am getting into my else and getting my error because obviously no ID was provided and the ball won't exist.
Any help/insight will be appreciated.
Edit: I think it may be sending it to the wrong method. I think that /api/v1/ball/specs is being sent to BallController#getIndex instead of BallController#getSpecs.
The problem is in this part of the routing:
Route::get('{id}', 'BallController#getIndex');
Route::get('specs', 'BallController#getSpecs');
When it sees "/api/v1/ball/specs", it will actually call getIndex() with the "id" parameter set to "specs", because that's the first matching route. One way to fix it would be to define the "specs" route first before you define the "{id}" route. But a better solution would be to limit the accepted values of the "{id}" parmeter like this...
Route::get('{id}', 'BallController#getIndex')->where('id', '[0-9]+');
One other suggestion, there shouldn't be a leading "/" in your prefix values (apparently the code may be working anyway, but they aren't really supposed to be there. So the complete updated route might look like this...
Route::group(['prefix' => 'api/v1', 'before' => 'auth.basic'], function() {
Route::group(['prefix' => 'ball'], function() {
Route::get('/', 'BallController#getIndex');
Route::get('specs', 'BallController#getSpecs');
Route::get('{id}', 'BallController#getIndex')->where('id', '[0-9]+');
Route::get('{id}/specs', 'BallController#getSpecs')->where('id', '[0-9]+');
});
});
Related
I have this route defined
Route::get('test/something/{id?}', 'MyController#mymethod')->name('test.something');
So if go to domain_1.com/test/something/123 I get some page with data.
Now, I want to to show the exact same thing if the site if accessed form another domain with subdomain. I've defined this:
Route::group(['domain' => 'subdomain.domain_2.com'], function() {
Route::get('/', 'MyController#mymethod', ['id' => 123]);
});
but I got to subdomain.domain_2.com, I get Too few arguments error. How can I pass the id parameter to the method in the route?
You could use redirect like:
Route::get('test/something/{id?}', 'MyController#mymethod')->name('test.something');
Route::group(['domain' => 'subdomain.domain_2.com'], function() {
Route::get('/', function(){
return redirect('/test/something/123');
});
});
If you don't want to use redirect, you could check the current url from the method in the controller like:
public function mymethod($id=null){
$currentUrl = request()->url();
if (strpos($currentUrl, 'subdomain') !== false) {
$id = 123;
}
// your function
}
I've found this piece of code that gets the job done, but I'm not sure is the correct way to achieve what I want
Route::group(['domain' => 'subdomain.domain_2.com'], function()
{
Route::get('/', [ 'as' => 'some_alias', function()
{
return app()->make(App\Http\Controllers\MyController::class)->callAction('mymethod', $parameters = [ 'id' => 123 ]);
}]);
});
I am using apiResource and other routes. I grouped them like below:
Route::group(['prefix' => 'posts'], function () {
Route::group(['prefix' => '/{post}'], function () {
Route::put('lablabla', [PostController::class, 'lablabla']);
});
Route::apiResource('/', PostController::class, [
'names' => [
'store' => 'create_post',
'update' => 'edit_post',
]
]);
});
all apiResource routes except index and store do not work! How should I group routes?
Your syntax for routing is wrong,
Notes
You will provide a uri for the apiResource (plural)
eg. Route::apiResource('posts', PostController::class);
Your name of resource route is wrong
Get this out https://laravel.com/docs/8.x/controllers#restful-naming-resource-routes
it should be
Route::apiResource('posts', PostController::class)->names([
'store' => 'create_post',
'update' => 'edit_post',
]);
No need of repeating Route::group, you can just write your routes like this
Route::prefix('posts')->group(function () {
Route::put('lablabla', [PostController::class, 'lablabla']);
});
Route::apiResource('posts', PostController::class)->names([
'store' => 'create_post',
'update' => 'edit_post',
]);
Your syntax is incorrect, there is a names method. See the documentation here https://laravel.com/docs/8.x/controllers#restful-naming-resource-routes.
These are my routes for which I am facing problem
Route to get the list of registered places in a particular city
Ex: http://localhost:8000/London, http://localhost:8000/London/Restaurants
Route::group(['namespace' => 'Page'], function() {
Route::group(['prefix' => '{city}', 'where' => ['city' => '[\w\d]+']], function() {
Route::get('/', 'CityPageController#showCityPage')->name('cityPage');
});
});
Route to get a particular user profile and its details such as reviews, photos etc.
Ex: http://localhost:8000/John, http://localhost:8000/John/reviews, http://localhost:8000/John/photos
Route::group(['namespace' => 'User'], function() {
Route::group(['middleware' => 'verified'], function() {
Route::group(['prefix' => '{username}', 'where' => ['username' => '[\w\d]+']], function() {
Route::get('/', 'ProfileController#showProfilePage')->name('profilePage');
Route::get('/reviews', 'ReviewController#showReviewPage')->name('reviewPage');
Route::get('/photos', 'ImageController#showPhotoPage')->name('photoPage');
});
});
});
The problem is that both of these routes are not working simultaneously.
The route the resides above the other takes precedence over the other route.
how to solve this problem of routing.
Edit
I know there is a way to achieve this functionality but I don't know how. Any help is appreciated.
Note: If you haven't, firstly I recommend to create a unique slug field on database first that will appear on url
Your route file
Route::get('{slug1}', 'PageController#singleSlug'); # slug 1 has to be unique i.e. username and cityname
Route::get('{slug1}/{slug2}', 'PageController#doubleSlug'); # combination of slug1 and slug2 has to be unique
The controller functions
public function singleSlug($slug1)
{
$user = User::where('name', $slug1)->first();
if ($user) {
return view('user')->compact('user');
}
$city = City::where('name', $slug1)->first();
if ($city) {
return view('city')->compact('city');
}
abort(404); # neither user nor city
}
public function doubleSlug($slug1, $slug2)
{
// check the slug2 as this value is always defined by the system
switch ($slug2) {
case 'Restaurants':
$city = City::with('restaurants')->where('name', $slug1)->first();
if ($city) {
$viewName = 'city_restos_listing';
$viewData = $city;
}
break;
case 'reviews':
$user = User::with('reviews')->where('name', $slug1)->first();
if ($user) {
$viewName = 'user_reviews_listing';
$viewData = $user;
}
break;
case 'photos':
$user = User::with('photos')->where('name', $slug1)->first();
if ($user) {
$viewName = 'user_photos_listing';
$viewData = $user;
}
break;
default:
abort(404); # the slug 2 is incorrect
break;
}
if(isset($viewName)) {
return view($viewName)->compact('viewData');
} else {
abort(404); # user or city not found
}
}
from Laravels point of view, both urls are the same:
{property}/
having different property names city and username doesn't make a differance because laravel will not understand that london is a city and say Prateek is a username.
A better approach I would suggest is to add an identefier of the model name before the prefix: EX. Route::group(['prefix' => 'users/{username}' ... instead of your approach and city before the city route.
have a look at this: https://laravel.com/docs/6.x/controllers#resource-controllers
add a prefix to distinguish these two kinds of route
Route::group(['prefix' => 'users/{username}'
and Route::group(['prefix' => 'cities/{city}'
An easy way to solve this issue :
Route::group(['namespace' => 'Page'], function() {
Route::group(['prefix' => 'city/{city}', 'where' => ['city' => '[\w\d]+']], function() {
Route::get('/', 'CityPageController#showCityPage')->name('cityPage');
});
});
Route::group(['namespace' => 'User'], function() {
Route::group(['middleware' => 'verified'], function() {
Route::group(['prefix' => 'profile/{username}', 'where' => ['username' => '[\w\d]+']], function() {
Route::get('/', 'ProfileController#showProfilePage')->name('profilePage');
Route::get('/reviews', 'ReviewController#showReviewPage')->name('reviewPage');
Route::get('/photos', 'ImageController#showPhotoPage')->name('photoPage');
});
});
});
I have setup multi-domain routing in my laravel 5.2 app. What I want to achieve is if a user hits, membership.app, he should be served different homepage as compared to user who hits, erp.app domain.
Route::pattern('erp', 'erp.app|erp.domain.com');
Route::pattern('membership', 'membership.app|membership.domain.com');
Route::group(['middleware' => ['web', 'auth'], 'domain' => '{erp}'], function() {
Route::get('/', 'HomeController#getIndex');
Route::controller('members', 'MembersController');
Route::controller('users', 'UsersController');
Route::controller('settings', 'SettingsController');
});
Route::group(['middleware' => 'web', 'domain' => '{erp}'], function () {
Route::controller('auth', 'Auth\AuthController');
});
Route::group(['middleware' => 'web', 'domain' => '{membership}'], function () {
Route::controller('/', 'BecomeMemberController');
});
Route::group(['middleware' => 'web'], function () {
Route::controller('ajax', 'AjaxController');
});
I tried this setup, but it breaks the code with first param in each controller method being the url instead of intended value.
Suppose I have a method hello in members controller.
public function hello($param1, $param2)
{
....
}
If I access erp.app/members/hello/1/2 url and try to print out $param1 of controller method, it returns erp.app instead of intended 1 in this case.
Please help.
I don't know why aren't you seperating the routes to different controllers as you say the output will be quite different...
A quick example of to use that:
Route::group(['domain' => '{type}.myapp.com'], function () {
Route::get('members/hello/{id1}/{id2}', function ($type, $id1, $id2) {
// when you enter --> members.myapp.com/hello/12/45
var_dump($type); //memebers
var_dump($id1); //12
var_dump($id2); //45
});
});
I'm creating an API that is available only via POST. I'm planning to have more than one version of the API, so the current one uses v1 as part of the URL.
Now, in case an API call is made via GET, PUT or DELETE I would like to return a Fail response. For this I'm using Route::match(), which works perfectly fine in the code below:
Route::group(['namespace'=>'API', 'prefix' => 'api/v1', 'middleware' => 'api.v1'], function() {
Route::match(['get', 'put', 'delete'], '*', function () {
return Response::json(array(
'status' => 'Fail',
'message' => 'Wrong HTTP verb used for the API call. Please use POST.'
));
});
// User
Route::post('user/create', array('uses' => 'APIv1#createUser'));
Route::post('user/read', array('uses' => 'APIv1#readUser'));
// other calls
// University
Route::post('university/create', array('uses' => 'APIv1#createUniversity'));
Route::post('university/read', array('uses' => 'APIv1#readUniversity'));
// other calls...
});
However, I noticed that I could group the routes even more, to separate the API version and calls to specific entities, like user and university:
Route::group(['namespace'=>'API', 'prefix' => 'api'], function() {
Route::match(['get', 'put', 'delete'], '*', function () {
return Response::json(array(
'status' => 'Fail',
'message' => 'Wrong HTTP verb used for the API call. Please use POST.'
));
});
/**
* v.1
*/
Route::group(['prefix' => 'v1', 'middleware' => 'api.v1'], function() {
// User
Route::group(['prefix' => 'user'], function() {
Route::post('create', array('uses' => 'APIv1#createUser'));
Route::post('read', array('uses' => 'APIv1#readUser'));
});
// University
Route::group(['prefix' => 'university'], function() {
Route::post('create', array('uses' => 'APIv1#createUniversity'));
Route::post('read/synonym', array('uses' => 'APIv1#readUniversity'));
});
});
});
The Route::match() in the code above does not work. When I try to access any API call with e.g. GET, the matching is ignored and I get MethodNotAllowedHttpException.
Can I get the second routes structure to work with Route::match() again? I tried to put it literally everywhere in the groups already. Putting the Route::match() outside of the hole structure and setting path to 'api/v1/*' does dot work either.
If you use the post() function you don't need to deny manualy other verb.
What you can do is to create a listener for the MethodNotAllowedHttpException and display what you want. Or you can also use any() function at the end of your route's group to handle all route that is not defined.