Laravel 5: Returning simple data in controllers - php

I have an API controller, where I always return JSON. Returned status will always be 200, because for other cases I'm throwing exceptions and handle them globally. I used to return response()->json($content); in each controller's method, but I changed it to form JSON in the middleware. Now, I'm having return response($content); everywhere. I was thinking about simplifying it even more and just return $content; instead. It works, but I am not sure if it's reasonable solution.
Are there any traps behind this idea?

I believe this is a good solution since you are using a middleware. In routes you don't want to return data as JSON you can simply exclude them from running your middleware.
In the future if you want to return your data in another format you can even pass the format as an argument when running the middleware. For example:
Route::get('resource/{id}', ['middleware' => 'format:xml', function ($id) {
//
}]);

Related

Response JSON error - Type is not supported when using Eloquent

EDIT:
I outputted the array and #apokryfos had mentioned something about resources not being able to be serialized.
Here is how some debug output looks: (Removed some information that is more sensitive)
Stream in Timestamp
It is my timestamp causing the issue. If I do
unset($user["timestamp"]);
Then almost everyone's solution works. So, the real reason was the resources was in there for my timestamp. How do I stop or fix that? I tried to do
public $timestamps = false;
This did not have any changes.
I have read through the documentation and a few tutorials. Sadly, I can't seem to find any documentation on what is returned and what functions are available for use when using Eloquent. Maybe I am just missing it.
However, I am using this code in my controller.
public function find($userName){
$user = UserSecurity::where('userName', $userName)->get();
//dd($user);
return Response()->json(['data' => $user], 200);
}
This is my router code.
$router->get('/UserSecurity/find/{userName}', ['uses'=>'UserSecurityController#find']);
I know that it is pulling the correct data from the database as if I uncomment the dd($user) I can see it on screen. However, if I try to send a response through Response()->json(..) it fails with this screen.
Image of Exception
I know I am probably using Response() incorrectly, but I have actually tried a number of different ways. Some just show empty responses and some crash similarly.
I have tried removing get() which from what I have found just returns nothing as there are no results. I have tried Response($user) and it was empty. I have tried return Response()->json($user); with the same type unsupported error.
Thanks for any help.
EDIT:
Changing a few code for testing. I changed to this
public function find($userName){
$user = UserSecurity::where('userName', $userName)->get()->toJson();
$user = json_encode($user);
return Response($user);
}
This returns false . I am not sure where the boolean is coming from. The original dd($user) actually has the correct information from the DB, so I know it is doing the query correct.
I think you must add the following in the top of your controller
then this code will help you.
use Illuminate\Support\Facades\Response;
The error message Type is not supported is actually coming from PHP's JSON serializer. There are only a very few types that PHP cannot serialise and the one that seems to be causing the issue in your particular case is a stream resource.
In order to check what is actually serialized in your model you will need to call:
$user = UserSecurity::where('userName', $userName)->get();
$user->map->jsonSerialize()->dd();
jsonSerialize exists because all Laravel models implement the JsonSerializable interface.
This will dump a collection of arrays of what PHP will attempt to serialise as JSON. The contents of this array are recursively serialised.
The default implementation of JsonSerializable in Model will attempt to serialize all model attributes but first will attempt to cast and call all accessors on attributes. This may or may not cause issues. At any rate the solution here is to figure out why there's a resource being returned in the jsonSerialize method and figure out the best way to hide it. Normally you can hide attributes by using
protected $hidden = [ 'timestamp' ];
however from your question it seems that the answer may not be so straight forward so may need to dig deeper.
I think you must add the following in the top of your controller:
use Illuminate\Support\Facades\Response;
and the controller must be like:
public function find($userName){
$user = UserSecurity::where('userName', $userName)->get();
return Response::json($user, 200);
}
PLease try like this
return response()->json(['status' => true, 'data' => $user],200);
I am already using this in my code
Using where('...')->get() returns a collection which cannot be used in response()->json().
Try using:
public function find($userName){
$user = UserSecurity::where('userName', $userName)->first();
return response()->json(['data' => $user->toArray()], 200);
}
here is just a typo error, you must write response() in lowercase instead or Response() because Response is a class, while response() is a magic Laravel function which already instantiates the class Response, and that you can use to return a Response instance.

Preprocess lumen route parameters with urldecode

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.

Laravel: Transform JSON response data

Currently, I am returning data like so (basic example):
public function index()
{
return User::all();
}
However, I want to wrap my responses with some extra meta data, so that they look something like:
{
'success': true,
'data': {
... // Normal response
}
}
The success value would be something as simple as statusCode == 200.
I've read about using response macros, response factories, after middlewares, the Fractal library etc
Ideally it will work with all responses, e.g. returning Eloquent Models and Collections, as well as with Response::json($data).
What is the best / right way, at the time of Laravel 5.2, to achieve this?
Thanks.
If you're developing a API for Laravel, I'd recommend checking out Dingo. It is one of the most useful package for developing APIs. Dingo uses Fractal to transform the responses. In your application to add such metadata, you could use Transformers in Dingo. There are also a lot of events that you can listen to and modify your data. In your particular example, ResponseWasMorphed would be one of the event that you would listen to.
If you want to continue with your project without adding external package, you can override the response() method in your BaseController which could check and add the needed.
public function response($data, $status)
{
//calculate parameter based on $status
$success = ...
return response(array_merge($data, ['success' => $success]));
}
You can use the fractal package for that. Then you can run the response through a transformer and get the proper response. Here's the package for that. https://packagist.org/packages/league/fractal

How to create optional REST parameter in Laravel

I'd like my API to handle calls of the such:
/teams/colors
/teams/1/colors
The first would return all colors of all teams, the second would return colors of team 1 only.
How would I write a route rule for this in Laravel?
This should be simple using a laravel route.
Route::pattern('teamid', '[0-9]+');
Route::get('/teams/{teamid}/colors', 'controller#method');
Route::get('/teams/colors', 'controller#method');
Using the pattern, it lets you specify that a route variable must match a specific pattern. This would be possible without the pattern also.
I noticed you mentioned REST in the title. Note that my response is not using Laravel's restful routes system, but its normal routes system, but I'm sure this could be adapted to be restul, or work with the restful system.
Hope this helps.
Edit:
After a bit of looking around, you may be able to use this if you are using Route::resource or Route::controller.
Route::resource('teams', 'TeamsController');
Route::any('teams/{teamid}/colors', 'TeamsController#Method');
// Or to use a different route for post, get and so on.
Route::get('teams/{teamid}/colors', 'TeamsController#getMethod');
Route::post('teams/{teamid}/colors', 'TeamsController#postMethod');
Note: the resource word above can be replaced with ::controller.
*Note 2: I have not tested this and am unable to guarantee it would work, but it does seem possible.*
You may try something like this:
class TeamsController extends BaseController {
// GET : http://example.com/teams
public function getIndex()
{
dd('Colors of all teams');
}
// GET : http://example.com/teams/1/colors
public function getColorsById($id)
{
dd("Colors of team $id");
}
// This method will call the "getColorsById" method
public function missingMethod($parameter = array())
{
if(count($parameter) == 2) {
return call_user_func_array(array($this, 'getColorsById'), $parameter);
}
// You may throw not found exception
}
}
Declare a single route for both methods:
Route::controller('/teams', 'TeamsController');

Laravel return Response::view

Simple question, I think.
I have a route that calls a function in a controller. In that controller, I have a call to another controller function.
For example:
$_testing = with(new TestingController)->prepwork($variable1,$variable2);
Inside of TestingController prepwork(), if a condition matches, I
return Response::view(...);
Question - how come that isn't enough? the return just returns control back to the calling function (which makes sense), but how do I tell Laravel - stop what you are doing and output that view.
To make it work, I have to:
$_testing = with(new TestingController)->prepwork($variable1,$variable2);
return $_testing;
That doesn't really work as the prepwork is designed to do some heavy lifting and then output a result model. The view is only kicked off when there is an error.
And YES - I know I can do something like this:
if ($_testing->whatImCheckingForErrors) { return Response::view(...); }
I'm just trying to understand why the return Response::view doesn't end the processing... If that makes sense.
Only the very last return will end processing, because the application will receive this last return and render whatever you send in it.
If you call a method (1), that calls another method (2), that calls a third one (3), Laravel app will receive the View only when your return back from (1).
That's why when you added that last return it worked.
There are things you can do, like:
echo Response::view(...);
die;
It may work sometimes, but
BUT THIS IS REALLY BAD, DO NOT DO THIS UNLESS YOU'RE JUST TESTING THINGS!
Because Laravel won't be able to close everything and flush whatever it needs to and you can compromise your application and even server.

Categories