I'm using Fractal transformer to prettify my User model json return (using Laravel 5.3 + dingo api).
Previously I was returning my user model like this:
return response()->success(compact('user'));
and the json was returned like this
{
"errors": false,
"data": {
"user": {
"id": 2,
"first_name": "
Now I use a transformer, and return it like this:
return $this->response->item($user, new UserTransformer);
and the json looks like this:
{
"data": {
"id": 2,
"first_name": "Client",
"last_name": "Testing",
How do I wire things up with fractal to return the same structure as it was previously? All my unit tests expect the original format, and so they're failing like this:
1) LoginEmailTest::testLoginEmailSuccess
Unable to find JSON fragment
["errors":false]
within
[{"data":[{"user":{"a
Update
I think the obvious place to customize the response is in the Fractal default dataArraySerializer. But I'm not sure how to deal with the error parameter, in other words, how do I pass it an actual error if it exists rather than just hardcoded it to null?
so this is how i fixed it (but for some reason I'm not happy with this whole situation, being a laravel nuub yet having familiarity with rails/django etc.. something doesn't feel right about all of this:)
<?php
/*
* This is to address the problem asked here:
* https://stackoverflow.com/questions/48669423/how-to-make-the-error-false-appear-in-fractal-json-response
* basically we must add an error key to the top level of the json response
*
*
*/
namespace App\Http\Serializer;
use League\Fractal\Serializer\ArraySerializer;
class DataErrorArraySerializer extends ArraySerializer
{
/**
* Serialize a collection.
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function collection($resourceKey, array $data)
{
return ['data' => $data, 'errors' => false];
}
/**
* Serialize an item.
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function item($resourceKey, array $data)
{
return ['data' => $data, 'errors' => false];
}
/**
* Serialize null resource.
*
* #return array
*/
public function null()
{
return ['data' => [], 'errors' => false];
}
}
and in the controller:
$manager = new Manager();
$manager->setSerializer(new DataErrorArraySerializer());
$resource = new Item($user, new UserTransformer);
return $manager->createData($resource)->toArray();
so this is what it looks like in a successful http request:
{
"data": {
"id": 2,
"first_name": "Client",
"last_name": "Testing",
..
},
"errors": false
}
when an http error happens, it doesn't even execute that code anyways, it will return an error response (for example see this part of the code:
} catch (\JWTException $e) {
\Log::debug('Could not create token for login email: '. $email);
return response()->error(trans('errors.no_token'), 500);
}
and response is defined in public/index.php like so:
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
anyways again this is what an error looks like:
{
"message": "422 Unprocessable Entity",
"errors": {
"message": [
"The email must be a valid email address."
]
},
"status_code": 422
}
Related
About this code, define an API endpoint to get post detail:
/posts/{post}
public function show($post)
{
return DB::table('posts')->where('id', $post)->get()->map(function($s){
$s->type = __(Post::TEXT[$s->type]);
return $s;
});
}
It return json like this:
[
{
"id": 1,
"name": "Test"
}
]
Want to return data like this:
{
"id": 1,
"name": "Test"
}
So added ->first() to the end of the method:
public function show($post)
{
return DB::table('posts')->where('id', $post)->get()->map(function($s){
$s->type = __(Post::TEXT[$s->type]);
return $s;
})->first();
}
But got error:
(1/1) UnexpectedValueException
The Response content must be a string or object implementing __toString(), "object" given.
How to do?
To make sure that an api route correctly returns a json response, use the json response helper. This will try to parse any data and add the correct headers.
public function show($post)
{
$post = DB::table('posts')->where('id', $post)->get()->map(function ($s) {
$s->type = __(Post::TEXT[$s->type]);
return $s;
})->first()
return response()->json($post);
}
You can instead of returning just the map function result, you can cast that into an object like this:
$result = DB::...
return (object) $result;
It's the first time i am using validation in laravel. I am trying to apply validation rule on below json object. The json object name is payload and example is given below.
payload = {
"name": "jason123",
"email": "email#xyz.com",
"password": "password",
"gender": "male",
"age": 21,
"mobile_number": "0322 8075833",
"company_name": "xyz",
"verification_status": 0,
"image_url": "image.png",
"address": "main address",
"lattitude": 0,
"longitude": 0,
"message": "my message",
"profession_id": 1,
"designation_id": 1,
"skills": [
{
"id": 1,
"custom" : "new custom1"
}
]
}
And the validation code is like below, for testing purpose i am validating name as a digits. When i executed the below code, the above json object is approved and inserted into my database. Instead, it should give me an exception because i am passing name with alpha numeric value, am i doing something wrong:
public function store(Request $request)
{
$this->validate($request, [
'name' => 'digits',
'age' => 'digits',
]);
}
Please try this way
use Validator;
public function store(Request $request)
{
//$data = $request->all();
$data = json_decode($request->payload, true);
$rules = [
'name' => 'digits:8', //Must be a number and length of value is 8
'age' => 'digits:8'
];
$validator = Validator::make($data, $rules);
if ($validator->passes()) {
//TODO Handle your data
} else {
//TODO Handle your error
dd($validator->errors()->all());
}
}
digits:value
The field under validation must be numeric and must have an exact length of value.
I see some helpful answers here, just want to add - my preference is that controller functions only deal with valid requests. So I keep all validation in the request. Laravel injects the request into the controller function after validating all the rules within the request. With one small tweak (or better yet a trait) the standard FormRequest works great for validating json posts.
Client example.js
var data = {first: "Joe", last: "Dohn"};
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST",'//laravel.test/api/endpoint');
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(JSON.stringify(data));
project/routes/api.php
Route::any('endpoint', function (\App\Http\Requests\MyJsonRequest $request){
dd($request->all());
});
app/Http/Requests/MyJsonRequest.php (as generated by php artisan make:request MyJsonRequest)
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyJsonRequest extends FormRequest{
public function authorize(){
return true;//you'll want to secure this
}
public function rules(){
return [
'first' => 'required',
'last' => 'required|max:69',
];
}
//All normal laravel request/validation stuff until here
//We want the JSON...
//so we overload one critical function with SOMETHING LIKE this
public function all($keys = null){
if(empty($keys)){
return parent::json()->all();
}
return collect(parent::json()->all())->only($keys)->toArray();
}
}
Your payload should be payload: { then you can do
$this->validate($request->payload, [
'name' => 'required|digits:5',
'age' => 'required|digits:5',
]);
or if you are not sending the payload key you can just use $request->all()
$request->merge([
'meta_data' => !is_null($request->meta_data) ? json_encode($request->meta_data) : null
]);
validator = Validator::make($request->all(), [
'meta_data' => 'nullable|json'
]);
Use the Validator factory class instead using validate method derived from controller's trait. It accepts array for the payload, so you need to decode it first
\Validator::make(json_decode($request->payload, true), [
'name' => 'digits',
'age' => 'digits',
]);
Following the example of #tarek-adam, in Laravel 9 it would be:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyJsonRequest extends FormRequest{
public function authorize(){
return true;//you'll want to secure this
}
public function rules(){
return [
'first' => 'required',
'last' => 'required|max:69',
];
}
//All normal laravel request/validation stuff until here
//We want the JSON...
//so we overload one critical function with SOMETHING LIKE this
public function validationData()
{
if(empty($this->all())){
$res = [
'success' => false,
'message' => 'Check your request',
];
throw new HttpResponseException(
response()->json($res, 422)
);
}
return $this->all();
}
}
I'm using custom Magento Rest API, to create a new user, my postman request data is:
URL: http://megashopping.am/api/rest/register/store/1
content-type: application/json
Key: firstname
but I am having this error
{
"messages": {
"error": [
{
"code": 400,
"message": "Decoding error."
}
]
}
}
My XML file is
<extendrestapiregister translate="title" module="api2">
<group>extendrestapi</group>
<model>extendrestapi/api2_register</model>
<title>Register</title>
<sort_order>10</sort_order>
<privileges>
<guest>
<retrieve>1</retrieve>
<create>1</create>
</guest>
</privileges>
<attributes >
<firstname>First Name</firstname>
</attributes>
<routes>
<route_entity>
<route>/register/:id/store/:store_id</route>
<action_type>entity</action_type>
</route_entity>
<route_collection>
<route>/register/store/:store_id</route>
<action_type>collection</action_type>
</route_collection>
</routes>
<versions>1</versions>
</extendrestapiregister>
My Class is
<?php
/**
* Override for Magento's Catalog REST API
*/
class ITology_RestApi_Model_Api2_Register_Rest_Guest_V1 extends Mage_Api2_Model_Resource {
/**
* Create a customer
* #return array
*/
public function _create(array $data) {
return $firstName = $data['firstname'];
}
}
I am using a calendar in my project and I want to pass data from my Event model to view file in JSON format. I tried following but it didn't work and am not able to display the data properly
$events = Event::find()->where(1)->all();
$data = [];
foreach ($events AS $model){
//Testing
$data['title'] = $time->title;
$data['date'] = $model->start_date;
$data['description'] = $time->description;
}
\Yii::$app->response->format = 'json';
echo \yii\helpers\Json::encode($data);
But it only returns one model in that $data array, the final data should be in following format:
[
{"date": "2013-03-19 17:30:00", "type": "meeting", "title": "Test Last Year" },
{ "date": "2013-03-23 17:30:00", "type": "meeting", "title": "Test Next Year" }
]
When you write this:
\Yii::$app->response->format = 'json';
before rendering data, there is no need to do any additional manipulations for converting array to JSON.
You just need to return (not echo) an array:
return $data;
An array will be automatically transformed to JSON.
Also it's better to use yii\web\Response::FORMAT_JSON constant instead of hardcoded string.
Another way of handling that will be using ContentNegotiator filter which has more options, allows setting of multiple actions, etc. Example for controller:
use yii\web\Response;
...
/**
* #inheritdoc
*/
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['view', 'index'], // in a controller
// if in a module, use the following IDs for user actions
// 'only' => ['user/view', 'user/index']
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
],
];
}
It can also be configured for whole application.
Update: If you are using it outside of controller, don't set response format. Using Json helper with encode() method should be enough. But there is also one error in your code, you should create new array element like this:
$data = [];
foreach ($events as $model) {
$data[] = [
'title' => $time->title,
'date' => $model->start_date,
'description' => $time->description,
];
}
You can try like this:
$events = Event::find()->select('title,date,description')->where(1)->all()
yii::$app->response->format = yii\web\Response::FORMAT_JSON; // Change response format on the fly
return $events; // return events it will automatically be converted in JSON because of the response format.
Btw you are overwriting $data variable in foreach loop you should do:
$data = [];
foreach ($events AS $model){
//Make a multidimensional array
$data[] = ['time' => $time->title,'date' => $model->start_date,'description' => $time->description];
}
echo \yii\helpers\Json::encode($data);
in laravel 5 i made a new request named ApiRequest.
class ApiRequest extends Request
{
public function authorize() {
return $this->isJson();
}
public function rules()
{
return [
//
];
}
}
As you can see i am accepting only json data. And i am receiving the json in controller like this
public function postDoitApi(ApiRequest $payload) {
$inputJson = json_decode($payload->getContent());
$name = $inputJson->name;
}
Which is working fine. I am getting data in $name. But now i need to validate the input json.
I need to set validation rule in ApiRequest for the name key like this
public function rules()
{
return [
'name' => 'required|min:6'
];
}
Help me to do this. Thanks.
Laravel validates AJAX requests the same way. Just make sure you're setting one of these request headers on your request:
'Accept': 'application/json'
'X-Requested-With': 'XMLHttpRequest'
Validating any headers can be done in clean way two steps:
Step 1: Prepare header data to request data in prepareForValidation method.
public function prepareForValidation()
{
$this->merge([
"content_type" => $this->headers->get("Content-type"),
]);
}
Step 2: Apply any validation rules that you want, (Here, you want your data exact to be application/json. so
public function rules(): array
{
return [
"content_type" => "required|in:application/json",
];
}
Complete Example looks like:
/**
* Class LoginRequest
*
* #package App\Requests
*/
class LoginRequest extends FormRequest
{
public function prepareForValidation()
{
$this->merge([
"content_type" => $this->headers->get("Content-type"),
]);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules(): array
{
return [
"content_type" => "required|in:application/json",
];
}
}
You could use a validator method instead of rules method:
class ApiRequest extends Request
{
public function authorize() {
return $this->isJson();
}
public function validator(){
//$data = \Request::instance()->getContent();
$data = json_decode($this->instance()->getContent());
return \Validator::make($data, [
'name' => 'required|min:6'
], $this->messages(), $this->attributes());
}
//what happens if validation fails
public function validate(){
$instance = $this->getValidatorInstance();
if($this->passesAuthorization()){
$this->failedAuthorization();
}elseif(!$instance->passes()){
$this->failedValidation($instance);
}elseif( $instance->passes()){
if($this->ajax())
throw new HttpResponseException(response()->json(['success' => true]));
}
}
}
return $inputJson->toArray();
and then pass to validator
$name = ['name'=>'er'];
$rules = array('name' => 'required|min:4');
$validation = Validator::make($name,$rules);
you can put following function in your ApiRequest form request.
public function validator(){
return \Validator::make(json_decode($this->getContent(),true), $this->rules(), $this->messages(), $this->attributes());
}