LARAVEL cast colums JSON with feature test phpunit - mysql

does not pass me the last test that compares the field just entered with the one sent because json
I have the following fields:
public function up()
{
Schema::create('polls', function (Blueprint $table) {
$table->id();
$table->text('now');
$table->json('paramJson');
$table->enum('status', ['a', 'b', 'c','d'])->default('a');
});
}
Poll model:
protected $fillable=[
'now',
'paramJson',
];
//to cast that column from JSON to an array automatically (maybe it doesn't work)
protected $casts = [
'paramJson' => 'array',
];
PollsController.php
public function add(Request $request)
{
$poll = Poll::create($request->all());
return response()->json($poll, 201);
}
Feature/PollApiTest.php
$data = [
"now" => "Will Messi sign for City?",
"parameters" : {"a"=> "yes", "b"=> "no"},
];
$response = $this->post('/api/polls',$data);//add data in polls
$response ->assertStatus(201);// assert Ok
$response->assertJson($data);//assert Ok
$response = $this->get('/api/poll');//get index polls
$this->assertSame($dataDbTest[0]['paramJson'],$data['paramJson']);//this test fails
the last test fails because below the error
Failed asserting that Array &0 (
'a' => 'yes'
'b' => 'no' ) is identical to '{"a": "yes", "b": "no"}'.
if the last test I do it with json_encode:
$this->assertSame($dataDbTest[0]['paramJson'],json_encode($data['paramJson']));
--- Expected
+++ Actual ## ##
-'{"a": "yes", "b": "no"}'
+'{"a":"yes","b":"no"}'

I think this should be the case
$this->assertSame(json_decode($dataDbTest[0]['paramJson']),$data['paramJson']);
json_decode

Related

Laravel: Parse JSON object with array of "sub-objects" to model instance

In my (Laravel) application receive a JSON which looks like:
{
"name": "order 1",
"customer": "cus123",
"orderItems": [
{
"amount": 1,
"name": "cola",
"price": "2.10"
},
{
"amount": 3,
"name": "fanta",
"price": "2.00"
},
]
}
I have create 2 models in Laravel, one Order and one OrderItem. I want to parse the received JSON to one Order instance $order.
I can get this done so by doing this in my OrderController:
class OrderController extends Controller
{
public function store(Request $request) {
$order = new Order();
$order->forceFill($request->toArray());
}
}
It's possible to access properties now like $order->name and $order->customer in the store function of the controller. When i access the $order->orderItems i receive an array with "orderItemsbut as array, not as instance ofOrderItem`.
I want that $order->orderItems returns an array of OrderItem instances. I tried the following in Order but this does not work as 'orderItems' is not a OrderItem::class but is an array with multiple "OrderItems".
protected $casts = [
'orderItems' => OrderItem::class,
];
How can i achieve that $order->orderItems returns an array of OrderItem instances?
Thanks for any help in advance!
Try to add the following to your controller
validation
manual storing your Order
manual storing each of your order items
.
class OrderController extends Controller
{
public function store(Request $request)
{
$your_rules = [
'name' => 'required|string',
'customer' => 'required|string', // related to customer id ?
'orderItems' => 'array',
'orderItems.*.name' => 'string',
'orderItems.*.amount' => 'integer|gte:1',
'orderItems.*.price' => 'numeric|between:0,99.99',
];
$validated = $request->validate($your_rules);
$order = Order::create([
'name' => $validated['name'],
'customer' => $validated['customer'], // is this customer id or name ?
]);
// I assume you already declare relationship to OrderItem inside your Order model
foreach ($validated['orderItems'] as $orderItem) {
// this array only is optional
$orderItem = Arr::only($orderItem, ['name', 'amount', 'price');
$order->orderItems()->save($orderItem);
}
// reload saved order items
$order->load('orderItems');
dd($order);
}
}
You can also create multiple children in single command.
$order->orderItems()->saveMany([
new OrderItem(['name' => '...', ... ]),
new OrderItem(['name' => '...', ... ]),
]);
Read here for more info https://laravel.com/docs/9.x/eloquent-relationships#the-save-method
You can move this into your model as extra custom method.
For example:
public function saveOrderItems(array $orderItems): void
{
$this->orderItems()->saveMany($orderItems);
}
And you call it as $order->saveOrderItems($orderItems);
P.S.
Dont forget to declare relationship in Order model.
public function orderItems()
{
return $this->hasMany(OrderItem::class);
}
I think you are confuse with the whole Model relationship. Checkout the documentation here, you need to define proper relationship and foreign key between your Order and OrderItem model.
Then your model should be like this;
//Order.php
class Order extends Model {
protected $fillable = [
'name',
'customer',
];
public function items() {
return $this->hasMany(OrderItem::class);
}
}
//OrderItem.php
class OrderItem extends Model {
protected $fillable = [
'amount',
'name',
'price'
];
public function order() {
return $this->belongsTo(Order::class);
}
}
Then your store method
public function store( Request $request ) {
$request->validate([
'name' => 'required',
'customer' => 'required|exists:customers_table,id',
'orderItems' => 'required|array'
]);
$order = Order::create( $request->except('orderItems') );
$items = $order->items()->createMany( $request->input('orderItems') );
}

Boolean Values Not Saved, Laravel

I have some boolean fields sent with a POST request to a Laravel controller. However, every data in the request is saved except the booleans.
Model Fillables:
protected $fillable = [
'name',
'code',
'governorate',
'point',
'overnight',
'isResort',
'family',
'youth',
'status',
'description',
'user_id',
'price_currency',
'map',
'checkoutMessage',
'checkoutYoutubeVideo',
'max_person_count',
'more_person_price',
'age_limit',
'productYoutubeVideo',
'resortProductCount',
];
Controller
public function store(Request $request)
{
try{
$data = $request->all();
$data['user_id'] = auth('user-api')->user()->id;
$gev_id = Str::before($request->governorate_id, ',');
$product = RentProduct::create($data);
$product->update([
'code' => $gev_id.rand((int)0, (int)pow(2, 256)).$product->id,
]);
return $this->returnData("product", RentProduct::where('id', $product->id)->with('photos')->get());
}catch(\Throwable $th){
return $this->returnError($th->getCode() , $th->getMessage());
}
}
The strange problem is, when I die & dump the created product from store method, boolean appears in the right nature but it saved without that booleans.
Postman request
Don't send 'true' as a string, send 1 (true) or 0 (false) as a int. By the way don't forget to change your column type to tinyint(1) data type.

Laravel: validate json object

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();
}
}

Laravel 5 PHPUnit test json post

I am trying to test a JSON API following the officiel doc. I know the problem comes from the data array given to the POST request. The test works fine with a single level array like ['hello' => 'world'], so apparently the post function cannot handle complex structures ? What am I doing wrong here ?
Test:
public function testInsert()
{
$this->post(
'/test',
[
'content' => 'Hello world!',
'count' => [
'a' => 12.345678,
'b' => 12.345678
],
'user' => [
'id' => 1
]
],
['contentType' => 'application/json']
)->seeJsonEquals([
'status' => true
]);
}
Error:
unknown:myapp nobody$ phpunit
PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
E.
Time: 648 ms, Memory: 15.25Mb
There was 1 error:
1) APITest::testInsert
ErrorException: Invalid argument supplied for foreach()
/Users/nobody/myapp/vendor/laravel/framework/src/Illuminate/Support/Arr.php:487
/Users/nobody/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:231
/Users/nobody/myapp/tests/APITest.php:43
FAILURES!
Tests: 2, Assertions: 2, Errors: 1.
Controller:
public function store() {
$user = User::find(Input::get('user.id'));
// TODO: Validate input using JSON schema
if (empty($user))
$errors[] = 'User does not exist.';
if (empty(Input::get('content')))
$errors[] = 'Content is empty.';
$a = Input::get('location.latitude');
if ($a < 15)
$errors[] = 'A out of range.';
$b = Input::get('location.longitude');
if ($b < 20)
$errors[] = 'B out of range.';
if (empty($errors)) {
$post = new Post();
$post->content = Input::get('content');
$post->count()->create([
'a' => $a,
'b' => $b
]);
$user->posts()->save($user);
$post->save();
return APIUtils::makeStatusResponse(true);
}
return APIUtils::makeStatusResponse(false, $errors);
}
I re-installed my Laravel application using the laravel new myapp command and everything works fine now. I am assuming the problem was coming from the previous install made with composer create-project --prefer-dist laravel/laravel may.

How to create associative array in Yii2 and convert to JSON?

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);