Laravel parse request into Eloquent model with relations - php

Im developing a Laravel API that will host and manage the data for a mobile application. The application that I am writing will make AJAX requests and send over JSON data to the Laravel controller.
This works fine for a basic model however I am unable to get this working for nested models. Ill explain:
So I have the following model structure:
Shelf ---- Has Many ----> Boxes ---- Has Many ----> Products
Shelf:
class Shelf extends Model
{
protected $fillable = ['name', 'location'];
public function boxes()
{
return $this->hasMany('App\Box');
}
}
Box:
class Box extends Model
{
protected $fillable = ['name', 'size', 'label'];
public function shelf()
{
return $this->belongsTo('App\Shelf');
}
public function products()
{
return $this->hasMany('App\Product');
}
}
Product:
class Product extends Model
{
protected $fillable = ['name', 'price', 'quantity'];
public function box()
{
return $this->belongsTo('App\Box');
}
}
All my models have validation checking before anything is saved.
I send the following request to my Laravel controller:
{
"name":"Shelf 1",
"location":"LOC1"
"boxes":[{
"name":"box 1",
"size": 130,
"label": "B1",
"products":[
{"name":"Prod1","price":23.00,"quantity":5},
{"name":"Prod2","price":13.00,"quantity":2}
]
}, {
"name":"box 2",
"size": 130,
"label": "B2",
"products":[
{"name":"Prod3","price":3.00,"quantity":15},
{"name":"Prod4","price":7.00,"quantity":8}
]
}, {
"name":"box 3",
"size": 160,
"label": "B3",
"products":[
{"name":"Prod5","price":103.00,"quantity":9},
{"name":"Prod6","price":83.00,"quantity":1}
]
}]
}
When I receive the above data in my Laravel controller i use:
$shelf = new Shelf;
$shelf->fill($request->all());
$shelf->save();
To get all of the data however this will only save the Shelf and not any of the relationships. Is there a common way (or library) I can use to parse the JSON within the Laravel controller?

As far as I'm aware Eloquent doesn't offer anything like that. You would need to save each one individually. May I suggest something like this:
$shelf = Shelf::create($request->only(['name', 'location']));
foreach ( $request->input('boxes') as $box )
{
$box = new Box($box);
$shelf->boxes()->save($box);
$pTemp = [];
foreach ( $box['products'] as $product )
{
$pTemp[] = new Product($product);
}
$box->products()->saveMany($pTemp);
}
Update
To minimise the number of queries run we can loop through all the boxes once, create them then loop through them again to create all the products. You're still having to run one query per box to create the products, but as far as I can see there is no way around that.
$shelf = Shelf::create($request->only(['name', 'location']));
$bTemp = [];
foreach ( $request->input('boxes') as $i => $box )
{
$bTemp[$i] = new Box($box);
}
$shelf->boxes()->saveMany($bTemp);
foreach ( $request->input('boxes') as $i => $box )
{
$pTemp = [];
foreach ( $box['products'] as $product )
{
$pTemp[] = new Product($product);
}
$bTemp[$i]->products()->saveMany($pTemp);
}

Related

Eloquent: Get specific data from pivot table when retrieving all

I have a many to many relationship between Users & Courses with a pivot table, Users_Courses, containing an isComplete value, but i can't seem to retrieve the isComplete value without looping through every user, getting their courses and then looping over every course getting the pivot data.
All the examples i have found is to map the isComplete value to the course with loops, but that seems like it's awfully taxing on the program and i don't really find it appealing which is why I'm making my own question here. If there's already an answer to this that i haven't seen please link it below as i can't seem to find it.
Also, I'm using Laravel-9 and MySQL.
The data structure I'm retrieving right now looks like this:
"data": [
{
"id": 2,
"fname": "name",
"lname": "last name",
"email": "mail#mail.com",
"courses": [
{
"id": 1,
"name": "test_course_1",
"description": "this is a test course for testing"
},
{
"id": 2,
"name": "test_course_2",
"description": "this is also a test course"
},
{
"id": 3,
"name": "test_course_3",
"description": "this course is a test course"
}
]
}
]
I'm searching for a way to retrieve the pivot value isComplete with Eloquent and getting the data with the course itself like this or something like it.
In other words, I want to check if the user has completed the course or not through the pivot table value isComplete as shown in the example below.
"data": [
{
"id": 2,
"fname": "name",
"lname": "last name",
"email": "mail#mail.com",
"courses": [
{
"id": 1,
"name": "test_course_1",
"description": "this is a test course for testing",
"isComplete": 1
},
{
"id": 2,
"name": "test_course_2",
"description": "this is also a test course",
"isComplete": 0
},
{
"id": 3,
"name": "test_course_3",
"description": "this course is a test course",
"isComplete": 0
}
]
}
]
The code i have right now looks like this:
class User extends Authenticatable
{
public function courses()
{
return $this->belongsToMany(Course::class, 'user_courses')
->withPivot('isCompleted');
}
}
class Course extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'user_courses')
->withPivot('isCompleted');
}
}
class UserController extends Controller
{
public function getUsersById(int $user_id)
{
try {
$users = User::where('id', $user_id)
->with('courses')
->get();
return response()->json([
'success' => true,
'data' => $users
]);
} catch (Throwable $th) {
return response()->json([
'success' => false,
'data' => null,
'message' => $th,
]);
}
}
}
I am aware that it's called isCompleted in the code, but it's also called that in the database. It's a typing error which haven't yet been fixed :D
In other words, I want to check if the user has completed the course or not through the pivot table value isComplete as shown in the example below.
Did you read about filtering using Pivot table columns in the docs: https://laravel.com/docs/9.x/eloquent-relationships#filtering-queries-via-intermediate-table-columns
If you need only completed courses you can call relation as
$users = User::where('id', $user_id)
->with(['courses' => function($query) {
$query->wherePivot('isCompleted', 1); // use quotes if its datatype is enum in database.
}])
->get();
Or you can make customized relations for completed, Incompleted in your Model.
class User extends Authenticatable
{
public function courses()
{
return $this->belongsToMany(Course::class, 'user_courses')
->withPivot('isCompleted');
}
public function completedCourses()
{
$this->courses()->wherePivot('isCompleted', 1);
}
public function InCompleteCourses()
{
$this->courses()->wherePivot('isCompleted', 0);
}
}
And in user controller you can call them as
$users = User::where('id', $user_id)
->with('completedCourses')
->get();
if you want the output to be like the JSON:
$user = User::with("courses")->find(1);
$user = $user->courses->each(
function($course) {
$course->isComplete = $course->pivot->isComplete;
unset($course->pivot);
}
);
this line will retrieve Courses with an object pivot, which includes the columns of your pivot table.
(Example)

How to update foreign key ide value in Laravel Eloquent?

I'm quite new to Laravel and I was not able to find the answer to this problem neither on Laravel docs, nor here.
I guess it's just a matter of how to search for it, cause I'm pretty sure it's a common case.
I have two models in relationship (this is a simplified case), I retrieve the info I need through a Resource file, but I'm not able to understand how to properly store or update info.
Here's a code example:
Models\Company.php
class Company extends Model
{
protected $fillable = [
'name', 'blablabla', 'country_id', 'blablabla2',
];
public function country() {
return $this->belongsTo(Country::class);
}
}
Models\Country.php
class Country extends Model
{
protected $fillable = [
'code', 'name', 'prefix', 'tax_code_id',
];
public function companies() {
return $this->hasMany(Company::class);
}
}
Then I have a CompanyController file to manage API requests:
Controllers\CompanyController.php
class CompanyController extends BaseController
{
public function index()
{
$companies = Company::paginate();
$response = CompanyResource::collection($companies)->response()->getData(true);
return $this->sendResponse($response, 'Companies retrieved successfully');
}
public function store(Request $request)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company = Company::create($input);
return $this->sendResponse($company->toArray(), 'Company added successfully.');
}
}
...
public function update(Request $request, Company $company)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company->update($input);
return $this->sendResponse($company->toArray(), 'Company updated successfully.');
}
And here the CompanyResource I'm using to display info as I need.
Resources/CompanyResource.php
class CompanyResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'blablabla' => $this->blablabla,
'country' => $this->country,
'blablabla2' => $this->blablabla2,
];
}
}
So when retrieving Companies (or single company) I get a nested JSON:
{
"id": "1",
"name": "something",
"blablabla": "blablabla",
"country": {
"id": "100",
"code": "MA",
"name": "Mars",
"prefix": "123",
"tax_code_id": "#$%"
},
"blablabla2": "blablabla2"
}
If I create or update a new company I send a payload that has the same structure of what I'm getting above, but if I edit country id value my company model doesn't get it.
PUT Api/companies/1
{
"name": "something",
"blablabla": "blablabla3",
"country": {
"id": "200",
"code": "JU",
"name": "Jupiter",
"prefix": "456",
"tax_code_id": "#=%"
},
"blablabla2": "blablabla2"
}
I'm expecting to update country_id field in companies table for record 1 so that it matches payload (so going from 100 to 200), but it's not happening.
I could edit frontend logic in order to send only country_id in payload since I'm not going to update countries table and all that additional info is redundant, but I'd like to know how to manage it in controller with Laravel.
Would you mind helping me? Thanks in advance.
If you want it to work with the code now, you need to have country_id in the root JSON object you are sending. As this is the way you would fill the id. This is not the best approach in my opinion, but this is why your update is not working at the moment.
{
"name": "something",
"blablabla": "blablabla3",
"country_id": 200,
...
I actually like the approach of sending complete objects. Commonly to fill id's is not good, as it can interfere with the way relations work. Laravel will set your relationships when you associate, if not you are not guaranteed to have the correct relationship after the fill.
Therefor i would fetch out the id and associate the country object with the company. In a logic similar to this.
// only fill non relation fields, fill used as save is needed after associate()
$company->fill($request->only(['name', 'blabla']));
$company->country()->associate(Country::find($request->get('country')['id']));
//associate does not save
$company->save();
I wrote a gist for this years ago that can relate any two models regardless of their relationship type. You just need to supply it with the name of the relationship method: https://gist.github.com/kmuenkel/055f107139d904e30810bf53750d9c6e

Laravel - Store JSON response in SQL database (via Guzzle)

First post! New to php & Laravel, figured I'd learn by creating a test project. I've been following Laracasts for guidance but run into issues.
What I'm looking to achieve:
Utilise Guzzle to call for an API
Store response into mySQL database
Setup a route for this
Setup a schedule so the controller runs once a day on schedule (I can start a new thread if need be)
I've got a controller setup for the Guzzle & storing data. I've got a database created which works as intended. The route I'm unsure about though on what exactly needs to be done so I'm struggling here on how to actually run the controller and store the data in the database.
I'd appreciate if anyone could review my code if I've done anything wrong, and give some guidance on routes in context of this.
Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
class DataController extends Controller
{
public function index()
{
$client = new Client(['base_uri' => 'https://api.ratings.food.gov.uk/ratings']);
$response = $client->request('GET', [
'headers' => [
'x-api-version' => '2',
'Accept' => 'application/json'
]
]);
$mydata = json_decode($response->getBody()->getContents(), true);
$object = new Object();
$object->ratingId = $mydata->ratingId;
$object->ratingName = $mydata->ratingName;
$object->ratingKey = $mydata->ratingKey;
$object->ratingKeyName = $mydata->ratingKeyName;
$object->schemeTypeId = $mydata->schemeTypeId;
$object->save();
Requests::insert($object);
}
}
?>
Migration
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRatingsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('ratings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->integer('ratingId');
$table->string('ratingName');
$table->string('ratingKey');
$table->string('ratingKeyName');
$table->integer('schemeTypeId');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('ratings');
}
}
API JSON Response Example
{
"ratings": [
{
"ratingId": 12,
"ratingName": "5",
"ratingKey": "fhrs_5_en-gb",
"ratingKeyName": "5",
"schemeTypeId": 1,
"links": [
{
"rel": "self",
"href": "http://api.ratings.food.gov.uk/ratings/12"
}
]
},
{
"ratingId": 11,
"ratingName": "4",
"ratingKey": "fhrs_4_en-gb",
"ratingKeyName": "4",
"schemeTypeId": 1,
"links": [
{
"rel": "self",
"href": "http://api.ratings.food.gov.uk/ratings/11"
}
]
},
{
"ratingId": 10,
"ratingName": "3",
"ratingKey": "fhrs_3_en-gb",
"ratingKeyName": "3",
"schemeTypeId": 1,
"links": [
{
"rel": "self",
"href": "http://api.ratings.food.gov.uk/ratings/10"
}
]
}
}
You should leverage the power of Eloquent ORM. I've found some issues in your code. You need to remove unnecessary lines that you have written there in order to create an object. Considering that you have created the columns with same names as returned by the API response, and considering that your model name is Rating and it should be, here are my suggestions:
Your controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use App\Rating;
class DataController extends Controller
{
public function index()
{
$client = new Client(['base_uri' => 'https://api.ratings.food.gov.uk/ratings']);
$response = $client->request('GET', [
'headers' => [
'x-api-version' => '2',
'Accept' => 'application/json'
]
]);
$mydata = json_decode($response->getBody()->getContents(), true);
/* You don't need to create an object as you are already parsing the response as an array, so remove below lines */
// $object = new Object();
// $object->ratingId = $mydata->ratingId;
// $object->ratingName = $mydata->ratingName;
// $object->ratingKey = $mydata->ratingKey;
// $object->ratingKeyName = $mydata->ratingKeyName;
// $object->schemeTypeId = $mydata->schemeTypeId;
// $object->save();
Rating::create($mydata);
}
}
And add make the columns fillable in your Rating model by adding a protected static $fillable property to your Rating model:
protected static $fillable = ['ratingId', 'ratingName', 'ratingKeyName', 'schemeTypeId'];
If above solution isn't the one you liked, then you need to either treat $mydata as an array, i.e. do $mydata['ratingId'] to get ratingId, not $mydata->ratingId or remove true argument from json_decode() to parse the response as an object, not an array.

How to insert nested objects into MongoDB with Laravel 5?

I'm using Laravel 5 and MongoDB based Eloquent Jenssegers to develop an API to save and get data. I have a object called Player and inside I have other nested objects.
For example:
{
"idPlayer": "1",
"name": "John",
"lastname": "Doe",
"stats": {
"position": "lorem",
"profile": "ipsum",
"technique": {
"skill": 1
}
}
}
Using Postman to test I've could insert "idPlayer", "name" and "lastname" without problems, but I couldn't figure out how to insert stats inside the Player object.
This is what I've tried:
PlayerController.php
public function store(Request $request)
{
$player->name= $request->input('name');
$player->lastname = $request->input('lastname');
$player->save();
return response()->json($player);
}
And to insert stats I've tried to do something like this inside the store function:
$player->stats = $request->input('position');
$player->stats = $request->input('profile');
But I get "Stats:null" on response and the name and lastname inserts ok.
I expect to insert the data just as the Player object shown above.
Make an array with keys.
public function store(Request $request)
{
$player->name = $request->input('name');
$player->lastname = $request->input('lastname');
$player->stats = [
'position' => $request->input('stats.position'),
'profile' => $request->input('stats.profile'),
];
$player->save();
return response()->json($player);
}
More data about PHP arrays.
Retrieving input from Laravel Requests.

Laravel : How do I fetch just the eager-loaded element and put them all in an array?

I have two models-ConversationsUser and Events-which are related in a many2many relationship. I want to eager load Events and put it all in an array. How do I do that?
These are my models and their relationships.
ConversationsUser
public function events(){
return $this->belongsToMany('Events');
}
Events
public function conversations(){
return $this->belongsToMany('ConversationsUser');
}
Controller (what I have done so far)
$loginuser = Auth::user();
$convUsers = ConversationsUser::with('Events')->where('user_id','LIKE',$loginuser->id)
->has('events');
$events = $convUsers->get()->fetch('events')->toJson();
unwanted result
[
[
{
"event_id":3
"conversaitons_id":1
}
],
[
{
"event_id":5,
"conversations_id":23
},
{
"event_id":6,
"conversations_id":23
}
]
]
Preferred result
[
{
"event_id":3
"conversations_id":1
},
{
"event_id":5,
"conversations_id":23
},
{
"event_id":6,
"conversations_id":23
}
]
I would suggest to do it the other way around, like this:
$conversation_ids = ConversationsUser::whereUserId($loginuser->id)->get(['conversations_id'])->toArray();
$events = Events::whereIn('conversations_id', $conversation_ids);

Categories