I am trying to do a small rest api which based on Lumen. Everything is ok for this sample:
routes/web.php
$router->post('foo/', function () {
return response('[]', 400)
->header('Content-Type', 'application/json');
});
postman receives this response:
400 - bad request. That's ok. But if I try to do the same with a controller php file:
routes/web.php
$router->post('accounts/', 'AccountController#register');
app/Http/Controllers/AccountController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class AccountController extends Controller {
public function __construct() {}
public function register(Request $request): string {
return response()->json(['message' => 'failed'], 400);
}
}
status code is: 200? But why?
Headers came in body response...
Can you advice something to solve this case?
Defining function's return type like string you tell php to make everything (unless strict_types declared) to convert anything to string. I presume somewhere in Response class there's a __toString method which outputs data as you see in your picture. So, just remove return type declaration. Or change it to Response. It is Response class's responsibility to process data and output it as required, not your controller:
public function register(Request $request)
{
return response()->json(['message' => 'failed'], 400);
}
Related
I am implementing a Rest API.
It turns out that when using Postman I send a GET request to a route which receives as a parameter account_id = 100
http://127.0.0.1:8000/api/balance?account_id=100
ID 100 exists but postman keeps returning 404 error with return "0".
I am using Laravel 8 and I think my problem is in the Handler. I show you the BalanceController controller and also the Handler class.
BalanceController:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Account;
class BalanceController extends Controller
{
public function show(Request $request)
{
$accountId = $request->input('account_Id');
$account = Account::findOrFail($accountId);
return $account->balance;
}
}
Handler.php class register Method
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
return response()->json('0',404);
});
}
The idea is that when I make this request, I will return the balance of said ID with the response 200, I repeat the ID = 100 exists since I create it by POST request at another time with the logic in another controller.
api routes.php
<?php
use Illuminate\Support\Facades\Route;
Route::post('/reset', [App\Http\Controllers\ResetController::class, 'reset']);
Route::get('/balance', [App\Http\Controllers\BalanceController::class,'show']);
Route::post('/event', [App\Http\Controllers\EventController::class, 'store']);
I would suggest that you implement this in Laravel's way. In your api.php, change your route so that it accepts a parameter like below.
Notice that {account_id} is then replaced with the actual ID number when a program hits this API route.
Route::get('/balance/{account_id}', [App\Http\Controllers\BalanceController::class,'show']);
and then, in your show() function, Add a second parameter like below:
public function show(Request $request, $account_id)
{
$account = Account::findOrFail($account_id);
return $account->balance;
}
I'm trying to return some JSON data from my controller. I actually got it working with.
response()->json(['success'=>true])->send();
but that is not the way it's done in the documentation. I tried
return \Response::json(['success' => true]);
and the status code was 200 but no data in the body. I guess it's okay but I just really want to know what the problem is. There were nothing in the log so there does not seem to be an error. If it's any help, I'm using Laravel 5.5.40 and a dependency called tymon/jwt-auth which applies some middleware for auth and refresh.
Should probably add that I have tried to simply return an array and string but the result stays the same.
CONTROLLER
Notice the commented section in createGame
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Game;
use Illuminate\Http\Response;
class GameController extends Controller
{
public function getPostPatchDelete(Request $req){
switch($req->method()){
case 'GET':
$this->getGame($req);
case 'POST':
$this->createGame($req);
case 'PATCH':
$this->updateGame($req);
case 'DELETE':
$this->deleteGame($req);
}
}
private function getGame($req){
}
private function createGame($req){
//Response::json(['success' => 'hi, atiq']);
//return response()->json(['lel'=>'lol'], 200);
response()->json(['success'=>true])->send();
}
private function updateGame($req){
}
private function deleteGame($req){
}
}
You have to use return in getPostPatchDelete() AND createGame():
public function getPostPatchDelete(Request $req){
switch($req->method()){
case 'POST':
return $this->createGame($req);
}
}
private function createGame($req){
return response()->json(['success'=>true]);
}
The way I always do it is like this:
return response()->json(['message' => 'Some message.'], 200);
Note: the second argument of the json() function is the status code.
Of course is always nice to use descriptive constants instead of numbers -in this case to set the HTTP response code- so:
use Illuminate\Http\Response;
class CoolController extends Controller {
public function coolFunction() {
// your logic
return response()->json(['message' => 'A message.'], Response::HTTP_OK);
}
}
I'm doing an existence check within a middleware, by checking a route-parameter.
If the check succeeds, I'm attaching it's model to the request to make it available throughout the rest of the request-cycle, application.
// App\Http\Middleware\CheckForExistence.php:
...
public function handle($request, Closure $next)
{
// some checks...
// success
$request->attributes->add([
'company' => $someModel
]);
}
I now have a controller which 'needs' this information in a couple of methods. So my thought was to add it to the construct of the controller and add it as a protected var in the whole controller:
// App\Http\Controllers\MyController.php
<?php
use Illuminate\Http\Request;
class MyController extends Controller
{
protected $company;
public function __construct(Request $request)
{
$this->company = $request->attributes->get('company');
}
public function index()
{
dd($this->company); // returns null
}
}
This controllers index() returns null instead of the give model.
If I change the index() method to:
public function index(Request $request)
{
return $request->attributes->get('company');
}
This returns the model; as expected.
Why is this happening? It looks like the middleware is not run when the controller is constructed.... Is there a way to circumvent it?
Or am I missing the obvious here.....
I could off course repeat myself in each method; but that is not very DRY ;)
You can't access the session or authenticated user in your controller's constructor because the middleware has not run yet, So you can do it like this :
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->company = $request->attributes->get('company');
return $next($request);
});
}
For reasons currently unclear to me, the controller object is constructed before the request changes are reflected in the request object. In short the request is not considered properly constructed when a controller is constructed. This post seems to imply that.
There's two ways to work around this (if for a second we ignore what you're trying to do).
Use request dependency injection
public function index(Request $request)
{
$compary = $request->attributes->get('company');
}
This is not really WET because you're just swapping $this->company with $request->attributes->get('company') it's just a refactor. You should be injecting the request in the controller action anyway and if you don't want to do that you can use the request() helper.
Use a callback middleware in the constructor (Maraboc's answer explains how)
Now if you want a more case specific solution though you can use case specific dependency injection:
If you need to bind a model to a specific route parameter you can use route model binding and add the following in your RouteServiceProvider (or any provider).
Route::bind("companyAsARouteVarName", function () {
// this is why more details in the question are invaluable. I don't know if this is the right way for you.
//checks
// success
return $someModel;
});
Then you will register your route as:
Route::get("/something/{companyAsARouteVarName}", "SomeController#index");
and your controller will be:
public function index(Company $companyAsARouteVarName) {
//Magic
}
Controller constructor will be initialized before middleware execution.
You can get data from Injected $request object in controller functions.
I want to set up the proper routes and models so when I visit www.myapp.com/search?keyword=test I get a view with data and when I visit api.myapp.com/search?keyword=test I get JSON representation of the exact same search result.
If I sort out the subdomain routes like so:
Route::group(array('domain' => '{subdomain}.myapp.com'), function()
{
Route::get('/', function($subdomain)
{
dd($subdomain);
});
});
What's the simplest way to use the same model method but instead of returning a view (for non-subdomain routes) with the array of model objects like so:
return view('search', compact('models'));
I'd return an array of JSON objects, simply like so:
return $models;
Note: a subdomain is not that necessary, it can also be myapp.com/api/search?keyword=test
Thanks.
There are several way to do this.
One option is to not use a separate subdomain or path at all. Instead, use the built-in mechanism to check which Content-Types are in the Accept header of the request.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SearchController extends Controller
{
public function getSearch(Request $req)
{
$keyword = $req->input('keyword');
$results = yourCodeThatDoesTheSearch();
// If the request has header `Accept: */json`, return JSON
if ($req->wantsJson())
{
return $results;
}
// Otherwise, return view
return view('search', compact('results'));
}
}
However, you may still wish to do it with a subdomain, either because you can't control the request headers, or because you want to have an easy indicator of what response you will get.
Here's the easiest way to do it with a subdomain:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SearchController extends Controller
{
public function getSearch(Request $req)
{
$keyword = $req->input('keyword');
$results = yourCodeThatDoesTheSearch();
// If the request came to the API subdomain, return JSON
if ($req->route('subdomain') === 'api')
{
return $results;
}
// Otherwise, return view
return view('search', compact('results'));
}
}
However, if you plan to do this in many places in your code, you should avoid repeating the same if statement everywhere.
Instead, create yourself a helper function:
<?php
use Illuminate\Contracts\Routing\ResponseFactory;
if (! function_exists('response_auto')) {
function response_auto($view, $data, $request, $status = 200, array $headers = [])
{
$factory = app(ResponseFactory::class);
if ($request->wantsJson())
{
return $factory->json($data, $status, $headers);
}
return $factory->view($view, compact('data'), $status, $headers);
}
}
You can then use it in your controller like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SearchController extends Controller
{
public function getSearch(Request $req)
{
$keyword = $req->input('keyword');
$results = yourCodeThatDoesTheSearch();
return response_auto('search', $results, $req);
}
}
Adding to Moshe Katz answer. If you just perform a $req->wantsJson() in your controller, works fine. However, there are occasions where if the user hits the back button, json will be returned in the view due to caching. A robust api would be in it's own controllers and use a repository for your db queries. That may be overkill.
Another option is to use a query parameter in your url.
Example:
in your ajax client (axios)
axios.get('/stuff', {params: {json: true}).then(response => return response);
this will send a request to
/stuff?json=true
in your laravel controller (StuffController#index)
public function index(Request $request)
{
$stuff = Stuff::all();
if ($request->query('json')) {
return $stuff;
}
return view('stuff.index', compact('stuff'));
}
This allows you to easily identify if the request wants json, prevents unwanted returns due to browser caching and keep the code in the same controller.
I am learning laravel and I have just created a middleware with a very little session work. but I am getting below error:
FatalThrowableError in VerifyCsrfToken.php line 136: Call to a member
function setCookie() on null
Here is the middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class Adminlogin {
public function handle($request, Closure $next) {
echo 1;
if (!$request->session()->has('userid')) {
return view('admin.auth.login');
}
return $next($request);
}
}
In this scenario response()->view() returns the specified view with the status code 200, but you are able to modify the response in many ways.
From the documentation: If you need control over the response's status and headers but also need to return a view as the response's content, you should use the view method: https://laravel.com/docs/5.3/responses
return response()
->view('hello', $data, 200)
->header('Content-Type', $type);
You shouldn't return a view from your middleware. Instead, try redirecting to a route that returns that view.
Like -
return redirect()->route('login');