Eloquent many to many relationship without middle table - mysql

I'm new to Eloquent and struggling mightily with the following.
In my database (mysql 5.7), there are 2 tables and structured as below.
article:
{
_id: 1,
title: "xxx",
content: "xxx",
tag_ids: [
4,
5
]
}
tag:
{
_id: 4,
tag: "tag1"
}
In ArticleModel, has a cast
protected $casts = [
"tags" => "array"
];
It is possible to make a many to many relationship without a middle table?
Any help would be greatly appreciated!

I created a package with JSON relationships: https://github.com/staudenmeir/eloquent-json-relations
You can create a many-to-many relationship like this:
class Article extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
protected $casts = [
'tag_ids' => 'array'
];
public function tags()
{
return $this->belongsToJson(Tag::class, 'tag_ids');
}
}
class Tag extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function articles()
{
return $this->hasManyJson(Article::class, 'tag_ids');
}
}

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

How can I format the array on a specific format in my laravel controller function?

I'm currently creating a laravel vue spa, and just wondering on how can I get designation names with these designation id's with the same structure. This is the json of designation id's:
[
[
1,
5
],
[
1,
3
]
]
This is my getDesignations function in EmployeesController.php:
public function getDesignations($id) {
$employee_designation_ids = Employees::find($id)->pluck('designation_id')->toArray();
$designation_name = [];
foreach ($employee_designation_ids as $employee_designation_id) {
$designation = Designations::where('id', '=', $employee_designation_id);
//$designation_name[] = $designation;
}
return $employee_designation_ids;
}
If you want specifically that format, you can do it like this (with a lot of guesses in my part since you did not share your Tables structures)
public function getDesignations($id) {
$employee_designation_ids = Employees::find($id)->designation_id;
return [[$id, $employee_designation_ids]];
}
But why are you returning a double table for a single row result.
Thanks for all your help! I've managed to fix it with this method in my controller:
public function getDesignations(Request $request, $id) {
$employee_designation_ids = Employees::where('id', $id)->pluck('designation_id');
return Designations::whereIn('id', $employee_designation_ids[0])->pluck('name');
}

Laravel data missing on query

I got some missing data from relationship query from specific id
I have 2 tables articles and comment
There is the model
Article :
class Article extends Model {
public $fillable = [
'title',
'description',
];
public function comments(){
return $this->hasMany(Comment::class, 'article_id');
}
}
Comment
class Comment extends Model {
public $fillable = [
'comment',
'article_id',
'user_id',
];
public function article(){
return $this->belongsTo(Article::class, 'article_id');
}
}
There is my query on getting all articles with comments:
Article::with('comments')->get();
This return all article but the comments missing; then i tried to update manualy the article_id on database for the comment table, this return all data well;
The things crazy about it, in others table and their relationships, there is somethink like, in get all, Some row data got his relationship data and other row has no relationship data in the same query while these data exist.
example i got:
[
{
id: '1',
name: 'name',
comments: [
{
id: '1',
name: 'name',
article_id: '1'
}
]
},
{
id: '2',
name: 'name',
comments: []
}
]
While in database this object with id 2 have data in his relation, and the object with id 1 get his relation foreign key updated manualy.
I use laravl 6.2, i don't know if it's related

Laravel Eloquent eager loading multiple relationships to same model generates multiple queries

I am currently working on a project where translations are a core feature. Implementing this with Eloquent has been a great experience so far, but the problem unfolds when a model has multiple relations to one model.
Say you have a model called Post:
<?php
namespace App;
// posts (
// id
// title
// content
// )
class Post extends \Illuminate\Database\Eloquent\Model {
public function title() {
// posts.title -> translations.reference
return $this->hasMany('App\Translation', 'reference', 'title');
}
public function content() {
// posts.content -> translations.reference
return $this->hasMany('App\Translation', 'reference', 'content');
}
}
And a Translation model:
<?php
namespace App;
// translations (
// id
// reference
// language
// value
// )
class Translation extends \Illuminate\Database\Eloquent\Model {
public function language() {
return $this->hasOne('App\Language', 'id', 'language');
}
}
And finally a Language model:
<?php
namespace App;
// languages (
// id
// code
// name
// )
class Language extends \Illuminate\Database\Eloquent\Model { }
Then the following will successfully generate the expected output:
Post::with([
'title.language',
'content.language'
])->get();
[
{
"id":1,
"title":[
{
"id":68,
"reference":"5efd07083a002",
"language":{
"id":1,
"code":"en",
"name":"English"
},
"value":"Title in english"
}
],
"content":[
{
"id":70,
"reference":"5efd0708f1c82",
"language":{
"id":1,
"code":"en",
"name":"English"
},
"value":"Lorem in English"
}
]
}
]
THE PROBLEM
Is that Eloquent sent duplicate (or very similar) queries to the database, making me wonder if there is a better solution:
[
{
"query":"select * from `translations` where `translations`.`reference` in (?)",
"bindings":[
"5efd07083a002"
],
"time":186.14
},
{
"query":"select * from `languages` where `languages`.`id` in (?, ?)",
"bindings":[
1,
2
],
"time":170.69
},
{
"query":"select * from `translations` where `translations`.`reference` in (?)",
"bindings":[
"5efd0708f1c82"
],
"time":171.93
},
{
"query":"select * from `languages` where `languages`.`id` in (?, ?)",
"bindings":[
1,
2
],
"time":142.38
}
]
After doing some research I could not find any answers to this problem except "it's not possible" with no explanation.
What is the best solution for this problem? Is there something that I am missing or is this really impossible?
Thankful for any answers!

Laravel many-to-many relationship OrderBy

I have 2 models in a Many to Many relationship. Let's say User and Role.
I want to sort my users based on the ASC/DESC of a field in Role.
My User & Role classes:
class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role','role_user');
}
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User','role_user');
}
I can sort the roles in each user but I cant sort the users
$query = User::with(array('roles'=> function ($query)
{
$query->select('role_name')->orderBy('role_name','asc');
}))->get();
I have also tried:
$query = User::with(roles)->orderBy('role_name','asc')->get();
But the error says column role_name does not exist.
Ideal result should look like this:
[
{
user_id:6
roles: [
"Admin",
"Baby"
]
},
{
user_id:2
roles: [
"Baby"
]
},
{
user_id:11
roles: [
"Baby",
"Cowboy"
]
}
]
I'd appreciate any help.
As user can have many roles i think you can concatenate role names and then order users by concatenated string. Try this:
User::selectRaw('group_concat(roles.name order by roles.name asc) as role_names, users.id')->
join('role_user','users.id','=','role_user.user_id')->
join('roles', 'roles.id','=','role_user.role_id')->
groupBy('user_id')->
orderBy('role_names','desc')->
get()
Please try the below modification in roles() function in User Class and then fetch it with User.
class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role','role_user')
->selectRaw('id,'role_name')
->orderby('role_name');
}
}
$query = User::with(roles)->get();
Hope this will be useful for you.