In my Laravel 8 project I have two tables A and B:
| A |
---------
| ID |
| B_ID |
| VALUE |
| B |
-----------
| ID |
| VALUE |
| Private |
In my models I have:
A{
public function b()
{
return $this->belongsTo(B::class);
}
}
B{
public function as()
{
return $this->hasMany(A::class);
}
}
In my controller I have:
public function index()
{
$a = A::join('bs', 'as.b_id', '=', 'b.id')->get(['as.value', 'bs.value']);
return $a;
}
Is'nt there a more elegant Laravel way to do this?
I would like to get all records from table A with the value from table B but without the private value from B. (There could be a lot of entries in Table A, Table B only has some values (<20) ).
When building a query, you may specify which relationships should be eager loaded using the with method. In your case you could write:
A::with('b:value')->('value')
In the with you can precise a specific relationship by its name or a list of column you want : https://laravel.com/docs/8.x/eloquent-relationships
The code I have provided as an example as not been tested and may need some changes but you have the general idea
Related
I'm struggling to connect three models in my Laravel app. The models are Bottle, Label and Spirit. I want to get all the labels based on bottle_id and spirit_id so I created a pivot table to store the relationships between Bottle-Label-Spirit. Please see below my current setup.
DB
+---------+--------+---------+-------------------------+
| bottles | labels | spirits | bottle_label_spirit |
+---------+--------+---------+-------------------------+
| id | id | id | id |
| name | name | name | bottle_id |
| | | | label_id |
| | | | spirit_id |
| | | | created_at |
| | | | updated_at |
+---------+--------+---------+-------------------------+
Where bottle_label_spirit is my pivot table
BOTTLE CLASS
class Bottle extends Model
{
public function labels() {
return $this->belongsToMany(Label::class)->withTimestamps();
}
public function spirits() {
return $this->belongsToMany(Spirit::class)->withTimestamps();
}
}
LABEL CLASS
class Label extends Model
{
public function bottles() {
return $this->belongsToMany(Bottle::class)->withTimestamps();
}
public function spirits() {
return $this->belongsToMany(Spirit::class)->withTimestamps();
}
}
SPIRIT CLASS
class Spirit extends Model
{
public function labels() {
return $this->belongsToMany(Label::class)->withTimestamps();
}
public function bottles() {
return $this->belongsToMany(Bottle::class)->withTimestamps();
}
}
QUESTIONS
So my questions are:
Is this the right approach to handle this manyToMany relationships?
If yes, how do i get all the labels where bottle_id = 1 and spirit_id = 1
You do not need a 4th table for this, you can accomplish all of this using hasManyThrough. This will allow you to query distant relationships.
Consider a table of nested locations which has columns for the location_id, its name and a parent_id. This parent field lets you know that you're in a nested location.
Example setup:
Locations
-----------------------------------
location_id | name | parent_id
-----------------------------------
1 | Isle A | NULL
2 | Column 23 | 1
3 | Shelf 2 | 2
4 | Box C | 3
Let's say we have an item sitting in location_id=4.
We now want to easily show the item's full location in a comma separated format. We expect to see:
Isle A, Column 23, Shelf 2, Box C
In order to achieve that, I have the following Laravel Model with a full_name() function which takes care of formatting and recursive calls:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
protected $table = 'locations';
protected $primaryKey = 'location_id';
protected $with = [
'location'
];
public function location()
{
return $this->hasMany('App\Location', 'parent_id', 'location_id');
}
public function full_name()
{
return ($this->location ? $this->location->full_name() . ', ' : '') . $this->name;
}
}
Nothing fancy soo far, works correctly and outputs what I need it to.
Now imagine down the line, some other piece of code allows for a mistake and the following happens:
Locations
-----------------------------------
location_id | name | parent_id
-----------------------------------
1 | Isle A | NULL
2 | Column 23 | 3
3 | Shelf 2 | 2
4 | Box C | 3
Notice that location #2's parent_id changed to 3. This creates an infinite loop of joins between 2 and 3. Laravel will not detect this recurrence (after all we're telling it to keep joining) and the execution will eventually die. This is my problem.
A possible solution I thought about would be to keep an ongoing list of "visited" location_id's, and if we stumble upon a repeat, we terminate the execution. If this was a "normal" function, I recon something like this would work:
public function location($recurrence_guard = [])
{
if(in_array($this->location_id, $recurrence_guard)) {
return collect([]); // or null?
}
array_push($recurrence_guard, $this->location_id);
return $this->hasOne('\App\Location(<somehow pass $recurrence_guard_in_here>)', 'location_id', 'parent_id');
}
Clearly this is nonsense and that's not how Laravel's relationships work. I'm looking for ideas on how to pass extra parameters into the relationships or for any better ideas on how to design this.
Thanks!
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.
I need to refactor project and I have problem. Below is old, working model, where 'active' column is in "people" table. I need to move 'active' column into "people_translations" table.
Do you have any Idea to modify scopeActive method?
Thanks a lot!
Old working model:
class BaseModel extends Eloquent
{
public function scopeActive($query)
{
return $query->where($this->table . '.active', '=', 1);
}
}
class People extends BaseModel
{
protected $table = 'peoples';
protected $translationModel = 'PeopleTranslation';
}
class PeopleTranslation extends Eloquent
{
public $timestamps = false;
protected $table = 'peoples_translations';
}
Old tables structure:
Table: peoples
id | type | date | active
-------------------------
7 | .... | ... | 1
Table: peoples_translations
id | people_id | language_id | name
-----------------------------------
1 | 7 | 1 | Ann
Old query:
$peoples = \People::active()->get();
New tables structure:
Table: peoples
id | type | date
----------------
7 | .... | ...
Table: peoples_translations
id | people_id | language_id | name | active
--------------------------------------------
1 | 7 | 1 | Ann | 1
Create a relation for translations inside People Model
public function translations()
{
return $this->hasMany('PeopleTranslation', 'people_id');
}
Create active scope in People model
public function scopeActive($query)
{
return $query->whereHas('translations', function($query) {
$query->where('active', 1);
});
}
It will make subquery for this table and as a result it will get where (count of translations with active = 1) > 0.
If you have one-to-one relation - look for hasOne relation method instead of hasMany.
Given the following table
gallery
+----+---------------+--------------+---------+
| id | gallery_title | viewcount | user_id |
+----+---------------+--------------+---------+
| 1 | Animals | 10 | 1 |
| 2 | Cars | 5 | 1 |
| 3 | Houses | 2 | 2 |
+----+---------------+--------------+---------+
user
+----+----------+
| id | username |
+----+----------+
| 1 | Bob |
| 2 | James |
+----+----------+
and the following classes
class Gallery extends Model
....
public function user()
{
return $this->belongsTo('App\User');
}
and
class User extends Model
....
public function galleries()
{
return $this->hasMany('App\Gallery');
}
calling $galleryCollections= Auth::user()->galleries; returns an array of collections in which I can iterate through
foreach ($galleryCollections as $galleryCollection)
{
$viewcount += $galleryCollection->viewcount;
}
print $viewcount; #returns 15
and so far everything works as expected, correctly up until this point.
However if I accidentally called $galleryCollection->sum('viewcount'), which is the last value from the iteration, the returned value is 17, as it's simply running the following SQL select sum('viewcount') as aggregate from 'gallery'.
I'm struggling to understand what exactly what is happening here. It's almost as if it's calling the 'sum()' method on the gallery class itself without passing in any 'where' values. I'd at least expect it to call the sum() method on the Collection, but instead it's going back to the database.
Is it because my Gallery class does not implement a 'sum()' method, and therefore it uses the Parent Model class and ignores the Gallery class?
If you want to count through sql, then:
Auth::user()->galleries()->sum('viewCount');
But, Auth::user()->galleries->sum('viewCount'); will sum on the collection Auth::user()->galleries.
Auth::user()->galleries() is a queryBuilder whereas Auth::user()->galleries is a collection of galleries of that user.