Use relationships without an extra model - php

I have 2 tables, stores and employees. The employees table is not persons, but just an estimate of the employees in a given store.
store
id | name | fk_employees
1 | Lewis | 2
employees
id | employees_amount
1 | 1-5
2 | 6-10
I could created an EmployeeCount model to define the relationship.
Store extends Eloquent {
public function employees() {
return $this->belongsTo('EmployeeCount');
}
}
EmployeeCount extends Eloquent {
}
However, it looks cluttered to me having to create an empty class.
Can I do this in a smarter way?

Related

Get results by finding an ID which is two joins away using Eloquent relationships

I need to get rows from one table using an id which is two joins away.
I know I can use join('table_name') but I am trying to use the model names rather than raw table names.
I'm trying to select shipping_shipment.* by joining order_item_join_shipping_shipment then joining order_item, and filtering where order_item.order_id = x
I tried this in the ShippingShipment class, but I can't figure it out.
return $this->hasManyThrough(OrderItem::class, ShippingShipment::class, 'shipment_id', 'order_item_id', 'id', 'id');
There are many items in an order, and many shipments. I need to get the shipments.
There can be more than one shipment per order - items come from various places.
There can be more than one shipment per item - if something is returned and needs shipping again.
The table I want to get rows from, shipping_shipment, is joined to order_item by a join table order_item_join_shipping_shipment. That join table has the order_item_id. I need then to join order_item table so that I can search for order_item.order_id
Table order_item model OrderItem
+-----+---------------+
| id | order_id |
+-----+---------------+
| 6 | 13464 |
| 8 | 13464 |
| 9 | 13464 |
+-----+---------------+
Table order_item_join_shipping_shipment model OrderItemJoinShippingShipment
+-----+---------------+-------------+
| id | order_item_id | shipment_id |
+-----+---------------+-------------+
| 1 | 6 | 12 |
| 1 | 9 | 12 | two items in one shipment
| | | |
| 2 | 8 | 13 |
| 3 | 8 | 14 | one item was returned so shipped again
+-----+---------------+-------------+
Table shipping_shipment don't need describing except to say it has an id column.
If I was to do it with MySQL it would look like this
SELECT ss.*, oiss.order_item_id FROM
order_item_join_shipping_shipment AS oiss
INNER JOIN shipping_shipment AS ss ON (oiss.shipment_id = ss.id)
INNER JOIN order_item AS oi ON (oiss.order_item_id = oi.id)
WHERE oi.order_id = 13464
I noticed you are not using the default table names, so your Models must have the table names explicit, e.g.:
class OrderItem extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'order_item';
}
In the same Model file of the above example, you need to indicate how the relationship works, i.e.:
public function shippingShipments()
{
return $this->belongsToMany(ShippingShipment::class, 'order_item_join_shipping_shipment', 'order_item_id', 'shipment_id');
}
Here you can check in Laravel documentation the whole explanation.
You need to apply the same concept in ShippingShipment Model, so your Model will be something like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'order_item';
/**
* The roles that belong to the user.
*/
public function orderItens()
{
return $this->belongsToMany(OrderItem::class, 'order_item_join_shipping_shipment', 'shipment_id', 'order_item_id');
}
}
This way you can get shipments by order item and vice-versa:
//Shipments of Order item 13464
$shipments = OrderItem::find(13464)->shippingShipments()->get();
//Order items of Shipment 1
$orders = ShippingShipment::find(1)->orderItems()->get();
Source: Laravel Documentation
As far as I can tell you are using a pivot table between ShippingShipment and OrderItem. If I understand you correctly you want to get OrderItems that are connected to ShippingShipment, if that is the case this is what you can do:
Make belongs to many relationships in both models, such as:
ShippingShipment:
public function orderItems(){
return $this->belongsToMany(OrderItem::class, 'table_name', 'column_id');
}
OrderItem:
public function shippingShipment(){
return $this->belongsToMany(ShippingShipment::class, 'table_name', 'column_id');
}
And then you can get the desired result by typing this query:
ShippingShipment::find(1)->with('orderItems')->get();
OrderItem::find(13464)->with('shippingShipments')->get();
Note: you can use orderItems:id,order or shippingShipment:id,some_other_field for more optimized query

Can we make a "Class Level Relationship" in Laravel, as opposed to an "Object Level Relationship"?

Consider the following scenario:
There are couple of entities in my Laravel application like the following:
Post
Page
Image
Video
All the above entities can have CustomFieldValues, which is another entity. The structure of the custom_field_values table is as follows:
ID
entity_id
custom_field_definition_id
value
[Timestamp fields]
All the CustomFieldValues belong to a single CustomFieldDefinition entity. Its table custom_field_definitions looks like following:
ID
parent_entity_name
definition_name
[Timestamp fields]
Following are some sample data from the custom_field_definitions table:
| ID | parent_entity_name | definition_name |
|----|--------------------|-------------------|
| 1 | Post | AuthorTwitterUrl |
| 2 | Page | SeoTitle |
| 3 | Image | OriginalSourceUrl |
| 4 | Video | MpaaRating |
As you can see, CustomFieldDefinitions are definitions of extra data, that we can store about each type of entity.
Following are some sampel data from the custom_field_values table:
| ID | entity_id | custom_field_definition_id | value |
|----|-----------|----------------------------|-----------------------------------|
| 1 | 1 | 1 | https://twitter.com/StackOverflow |
| 2 | 1 | 2 | My Page's SEO Title |
| 3 | 1 | 3 | http://example.com/image.jpg |
| 4 | 1 | 4 | G – General Audiences |
A little description about the data contained in the custom_field_values table:
CustomFieldValue:1: The value for CustomFieldDefinition:1 and its entity 1 (Post:1, in this case, because CustomFieldDefinition:1 is related to Post.) is "https://twitter.com/StackOverflow".
CustomFieldValue:2: The value for CustomFieldDefinition:2 and its entity 1 (Page:1, in this case, because CustomFieldDefinition:2 is related to Page.) is "My Page's SEO Title".
CustomFieldValue:3: The value for CustomFieldDefinition:3 and its entity 1 (Image:1, in this case, because CustomFieldDefinition:3 is related to Image.) is "http://example.com/image.jpg".
CustomFieldValue:4: The value for CustomFieldDefinition:4 and its entity 1 (Video:1, in this case, because CustomFieldDefinition:4 is related to Video.) is "G – General Audiences".
custom_field_values table's entity_id can refer to any entity class, therefore it is not a foreign key in the DB level. Only in combination with custom_field_definition_id we can find to which entity it actually refers to.
Now, all is well and good, until I need to add a relationship called customFieldDefinitions to any of the entities (Say Post.).
class Post extends Model {
public function customFieldDefinitions(){
$this -> hasMany ('CustomFieldDefinition');
}
}
The above does not work, because the datapoint that indicates the CustomFieldDefinition's relationship is not a foreign key field in the custom_field_definitions table, named post_id. We have to somehow build the relationship based on the fact that some records in the custom_field_definitions table has "Post" as the value of the field parent_entity_name.
CustomFieldDefinition::where('parent_entity_name', '=', 'Post');
The above snippet fetches the CustomFieldDefinitions that are related to the Post, however, it is not possible to do something like the following with the relationship:
class Post extends Model {
public function customFieldDefinitions(){
$this
-> hasMany ('CustomFieldDefinition')
-> where ('parent_entity_name', '=', 'Post')
;
}
}
The where constraint works. But Laravel also injects the ID of the current Post object into the set of constraints.
So, what I want to do is, not consider the current object's ID at all, and build a "Class Leavel Relationship", and not an "Object Level Relationship".
Is this possible under Laravel?
There might be a workaround but I'm not pretty sure about it.
What you could try doing is to define a mutated attribute and set it as the local key of the relationship:
class Post extends Model
{
public function getEntityNameAttribute()
{
return 'Post';
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
You could also go further and define a trait which could be used by all your models which have customFieldDefinitions. It could look like:
trait HasCustomFieldDefinitionsTrait
{
public function getEntityNameAttribute()
{
return (new ReflectionClass($this))->getShortName();
}
public function customFieldDefinitions()
{
return $this->hasMany(
'CustomFieldDefinition',
'parent_entity_name',
'entity_name'
);
}
}
Then you can use it wherever needed:
class Post extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Video extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Page extends Model
{
use HasCustomFieldDefinitionsTrait;
}
class Image extends Model
{
use HasCustomFieldDefinitionsTrait;
}
Instead of hasMany(), you can create One To Many (Polymorphic) relationship between Post, Page, Image, Video and CustomFieldDefinition.
More about polymorphic relationships here.

Laravel 5.6 - How to fetch last X guest names for room deal?

I can't figure out how to structure an efficient Eloquent query for the following scenario.
Users can stay in many locations like rooms, apartments, homes, so we have a polymorphic stayable_locations table, but we're only focusing on the room stayable_type of this table. When the staff clicks a room, we want to display all available room deals (if any are available from the room_deals table) and also the last 3 guests for each room deal (if any).
Trying to get this output from the following tables via eloquent:
Room 111 (desired output for room deals and guests below)
- Room Deal #1 -> Able, Kane, Eve
- Room Deal #2 -> Eve, Adam
------------------------------------------
$room = Room::where('id',111)->first(); // room 111
// Eloquent query, not sure how to setup model relations correctly
// To get last 3 guest names per room deal [if any] in an efficient query
$room->roomDeals()->withSpecificRoomDealLast3RoomGuestNamesIfAny()->get();
Here is the table structure:
stayable_locations table [polymorphic]:
id | stayable_id | stayable_type | room_deal_id | room_guest_id
----------------------------------------------------------------
1 | 111 | room | 0 | 3 (Steve no room deal)
2 | 111 | room | 1 | 1 (Adam room deal)
3 | 111 | room | 1 | 2 (Eve room deal)
4 | 111 | room | 1 | 4 (Kane room deal)
5 | 111 | room | 1 | 5 (Able room deal)
6 | 111 | room | 2 | 1 (Adam room deal)
7 | 111 | room | 2 | 2 (Eve room deal)
room_deals table:
id | room_id | room_deal
-----------------------
1 | 111 | Deal A
2 | 111 | Deal B
users table:
id | name
------------
1 | Adam
2 | Eve
3 | Steve
4 | Kane
5 | Able
UPDATE: Showing respective models
User Model:
class User extends Authenticatable {
public function stayableLocations() {
return $this->morphMany('App\StayableLocation', 'stayable');
}
}
RoomDeal Model:
class RoomDeal extends Model {
public function room() {
return $this->belongsTo('App\Room');
}
public function guests() {
return $this->belongsToMany('App\User', 'stayable_locations', 'room_deal_id', 'room_guest_id');
}
}
StayableLocation Model:
class StayableLocation extends Model {
public function stayable() {
return $this->morphTo();
}
public function room() {
return $this->belongsTo('App\Room', 'stayable_id');
}
}
Room Model:
class Room extends Model {
public function stayableLocations() {
return $this->morphMany('App\StayableLocation', 'stayable');
}
public function roomDeals() {
return $this->hasMany('App\RoomDeal');
}
}
Any idea how to get the desired output via an efficient eloquent query?
I figured it out from the helping comments in my question. Here we go:
Laravel does not have this out of the box (see here) so we'll have to use a third party package.
Install the staudenmeir/eloquent-eager-limit package per link directions and follow usage example.
This is what needed to change above [still used same defined relationship above for below ...], just added the use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;:
class User extends Authenticatable {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
...
}
class RoomDeal extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
...
}
Working query with nested limit via eloquent, thanks to the commenters and package help:
$room = Room::find(111);
$deals3Guests = $room->roomDeals() // query deals
->with(['guests' => function($query) { // eager load guests
$query->orderBy('stayable_locations.id', 'desc') // get latest guests
->limit(3); // limit to 3
}])
->get();

A little complex query using eloquent

I am trying to learn laravel and currently using eloquent to interact with the database. I am stuck on how I could use eloquent to get a kind of a join in eloquent.
I have a many to many relation between two tables :users and projects , I use sharedProject table to be the intermediate table .
The tables are as such
Users:
| iduser | name | password |
----------------------------------------
| 1 | somename | hashPassword |
| 2 | somename2 | hashPassword |
| 3 | somename3 | hashPassword |
| 4 | somename4 | hashPassword |
----------------------------------------
Projects:
| pid | projectname
-------------------
| 1 | somename
| 2 | somename
SharedProjects:
| pid | iduser | sharedProjectid |
----------------------------------
| 1 | 1 | 1 |
| 1 | 2 | 2 |
Now I want to get all the users who are not sharing a given project, for example in the above case for project with id 1 , I should get user 3 and user 4.
Here are my relationships in User model
/**
* User can have many projects
*
* #var array
*/
public function projects(){
return $this->hasMany('App\Project','pid','iduser'); // hasmany(model,foreignkey,localkey)
}
/**
* The user can have many shared projects
*/
public function sharedProjects()
{
return $this->belongsToMany('App\SharedProject', 'sharedProjects', 'iduser', 'pid');// belongsToMany('intermediate tablename','id1','id2')
}
and in the Project model:
/**
* The project can be shared by many users
*/
public function sharedProjects()
{
return $this->belongsToMany('App\SharedProject', 'sharedProjects', 'pid', 'iduser');// belongsToMany('intermediate tablename','id1','id2')
}
/**
* a project belongs to a single user
*
* #var array
*/
public function user()
{
return $this->belongsTo('App\User');
}
I would prefer a eloquent way to do this , but would also except it, if can't be done in eloquent and I have to see a alternate approach I would appreciate a plain mysql query as well.
Thanks
Once you define your Eloquent models and your many-to-many relationships, you can use them to get the data you're looking for.
Assuming a User model that has a projects relationship defined, you can use the whereDoesntHave() method to query for a list of users that are not related to a specific project.
$projectId = 1;
$users = User::whereDoesntHave('projects', function ($q) use ($projectId) {
return $q->where('projects.id', $id);
})->get();
You can read about defining many-to-many relationships in Eloquent here.
You can read about querying relationship existence here.
As you may notice, not all methods are documented (like whereDoesntHave()), so you may have to go source code diving. You can dig into the Eloquent codebase here.
I resort to use plain mysql queries, this seems to work for me:
$nonSharedUsers = DB::select( DB::raw("SELECT iduser FROM users WHERE NOT EXISTS (SELECT * FROM sharedProjects WHERE sharedProjects.iduser= users.iduser and sharedProjects.pid=:projectId)"), array(
'projectId' => $pid,
));

Laravel: use Eloquent belongsto , get data from both tables?

In Controller, I would like to pass the only one variable with specifies column from parent in it. Now,I'm using
View::make('store.product')->with('products', Product::find($id)
->join('design','product.design_id','=','design.id')
->join('user','design.user_id','=','user.id')
->select('user.name','design.title','product.price')
->get();
My question is
1.Is there a better way to do this by using Belongsto?
2.If can do, Is it work the same with Hasmany?
This is my table structure.
User
id | name
1 | 'John'
Design
id | user_id | title
1 | 1 | 'Chill'
2 | 1 | 'Mad'
Product
id | design_id | price
1 | 1 | 20
2 | 1 | 30
And Model be like this
Product belongsto Design ,
Design belongsto User
Add a method to your Users like so for your designs that the user has;
public function designs(){
$this->hasMany('Design');
}
For the designs model add the following methods;
public function user(){
$this->belongsTo('User');
}
public function products(){
$this->hasMany('Product');
}
For your products model
public function design(){
$this->belongsTo('Design');
}
These will set up the relationship allowing you to eager load the data on your models.
This can be done like so;
$variable = Product::with('designs')->find(1); //This will load the product with the related designs
If you want all the designs and the users that belong to the designs do the following;
$variable = Product::with('designs', 'design.user')->find(1); //This will load the designs that relate to the Product and include the user that that design belongs to on the Design model.
To access the properties use the following;
$variable->designs //This will return a collection of all the designs.
$variable->designs->first()->user //This will return the user model that the design belongs to.
An example of displaying the information;
#foreach ($variable->designs as $design)
{{ $design->user->username }}
#endforeach
Please note: i have not tested this code.

Categories