I have a Laravel 5.5 application and I have the following models.
Jobs
JobSheets
Users
I have a relation on my Users model
public function jobs() {
$this->hasMany('App\Job','user_id')->with('jobSheets');
}
I have a relation on my Jobs model
public function jobSheets() {
$this->belongsToMany('App\JobSheet','jobs_jobsheets','job_id','sheet_id');
}
I'm trying to return all my current logged in users' jobs with the latest job sheet.
A job can have many job sheets but I only want to return the latest.
I've tried in my controller
return Auth::user()->jobs;
However that returns all the jobs sheets against the job.
I've also tried creating another method on my job model.
public function latestJobsheet()
{
return $this->jobsheets->first();
}
and amending my jobs method on the user model to:
public function jobs() {
$this->hasMany('App\Job','user_id')->with('latestJobsheet');
}
but I get the following error:
Call to undefined method
Illuminate\Database\Query\Builder::addEagerConstraints()
You could split the BelongsToMany relationship and add a model for the pivot table (jobs_jobsheets):
class Job {
public function latestJobSheetPivot() {
return $this->hasOne(App\JobSheetPivot::class)->orderBy('sheet_id', 'DESC');
}
}
class JobSheetPivot {
protected $table = 'jobs_jobsheets';
public function jobSheet() {
return $this->belongsTo(App\JobSheet::class);
}
}
class User {
public function jobs() {
return $this->hasMany(App\Job::class)->with('latestJobSheetPivot.jobSheet');
}
}
Assuming your jobs_jobsheets table contains an id column it would probably be best to order the jobSheets returned from newest to oldest.
Here's what that would look like:
public function jobSheets()
{
$this->belongsToMany('App\JobSheet','jobs_jobsheets','job_id','sheet_id')->orderBy('id', 'DESC');
}
If you want to limit the number of results returned you can chain ->limit($num_results) to the end of the belongsToMany method
You can override the $with property on your Job model and have it eager load the relation latestJobsheet. From there, when you access the jobs relation on your User model, you should have the latest job sheet available to you. How you determine the latest job sheet is a separate matter.
Related
I have a model called RealEstate, this model has a relation with another model called TokenPrice, I needed to access the oldest records of token_prices table using by a simple hasOne relation, So I did it and now my relation method is like following:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function firstTokenPrice(): HasOne
{
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
By far it's fine and no complexity. But now, I need to involve another relation into firstTokenPrice.
Let me explain a bit more:
As my project grown, the more complexity was added it, like changing firstTokenPrice using by a third table called opening_prices, so I added a new relation to RealEstate called lastOpeningPrice:
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
So the deal with simplicity of firstTokenPrice relation is now off the table, I want to do something like following every time a RealEstate object calls for its firstTokenPrice:
Check for lastOpeningPrice, if it was exists, then firstTokenPrice must returns a different record of token_price table, otherwise the firstTokenPrice must returns oldestOfMany of TokenPrice model.
I did something like following but it's not working:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
I have checked the $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany() using by ->toSql() method and it returns something unusual.
I need to return a HasOne object inside of firstTokenPrice method.
You can use ofMany builder for that purpose:
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->ofMany([
'id' => 'min',
], function ($query) use ($lop) {
$query->where('amount', $lop->amount);
});
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
I used ->oldest() with a custom scope called amounted in TokenPrice model:
class TokenPrice extends Model
{
public function scopeAmounted(Builder $query, OpeningPrice $openingPrice): Builder
{
return $query->where('amount', $openingPrice->amount);
}
/....
}
And then changed my firstTokenPrice
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount);
return $this->hasOne(TokenPrice::class)->amounted($lop)->oldest();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
It's working, but I don't know if it's the best answer or not
I try to get latest 10 products with its images but only the first image
so that is what i try
$newProducts = \App\Product::latest()->with(['images', function($el){
$el->first();
}])->with('category')->take(10)->get();
but it gives me this error
mb_strpos() expects parameter 1 to be string, object given
it has a morph relation between product and image
Product Model
class Product extends Model {
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
}
Image Model
class Image extends Model {
public function imageable()
{
return $this->morphTo();
}
}
The above solutions are all good. I personally prefer a different solution that I think is gonna be ideal.
I am gonna define a different relationship for a product:
class Product extends Model {
public function images()
{
return $this->morphMany(Image::class, 'imageable');
}
public function firstImage()
{
return $this->morphOne(Image::class, 'imageable');
}
}
So you can access the first image directly or eager load the relationship:
$product->firstImage;
$product->load('firstImage');
Product::with('firstImage');
Just FYI, I learnt about this and other useful database tricks from Jonathan Reinink in Laracon Online 2018.
When using with as a key value array, the $el parameter to the closure will be a query builder that has not executed yet.
The way to limit query builders of results is to use take(). Therefor your code should look like this.
->with(['images', function($el) {
$el->take(1);
}])
Edit To make this solution work, you will need an extra package. Using the following trait should make it work and using limit instead. See the following post.
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
->with(['images', function($el) {
$el->limit(1);
}])
Alternatively Laravel solution is to use transformation like properties, where you can create your own custom properties, in the function naming starting with get and ending with attribute.
class Product {
protected $appends = ['first_image'];
public function getFirstImageAttribute() {
return $this->images->first();
}
}
Now if you use standard Laravel serialization all products will have an first_image field and in your code you can access it like so.
$product->first_image;
To avoid performance hits, include images using with('images').
public function images()
{
return $this->hasMany(Image::class);
}
public function firstImage()
{
return $this->images()->first();
}
Simply create a function that defines relationship between product and its images.
Then create a function that gets the first image
In my database, i have two tables notification and alertFrequency. The notification has field id and website_url and the alert frequency has id and notification_id. Both tables has models which is one to many. The notification can have one or many alertFrequency.
class Notification extends Model {
public function alertFrequencies() {
return $this - > belongsToMany('AlertFrequency::class');
}
}
namespace App;
use Illuminate\ Database\ Eloquent\ Model;
class AlertFrequency extends Model {
protected $table = 'alertFrequencies';
public function notification() {
return $this - > belongsTo(Notification::class);
}
}
in the notification model, i wrote a function called alert, that will give me the laastest alert associated with a specific websie.
public function alert(){
$alert_frequency = AlertFrequency::find('notification_id')->first();
$o_notification = $this->alertFrequencies()->where('notification_id',$alert_frequency->id)
->select('created_at')->orderBy('created_at')->first();
if($alert_frequency ==null){
return false;
}
return created_at;
}
Is this a right way to extract the data? i would appreciate any suggestions and helps?
Notification hasMany AlertFrequency
public function alertFrequencies(){
return $this->hasMany('App\AlertFrequency');
}
and,
$alert_frequency = AlertFrequency::with('notification')->orderBy('created_at','desc')->first();
loads the latest AlertFrequency along with it's notification.
See One to Many relationship and Eager loading in documentation.
to get laastest alert associated with a specific websie with url $website_url.
Notification::where('website_url', $website_url)->orderBy('created_at')->first();
hasMany relation :
public function alertFrequencies(){
return $this->hasMany('App\AlertFrequency','notification_id');
}
I'm attempting to make cascading soft deletes work by overriding each model's delete() function, which I think should cascade down from Project to Version to Link, but the problem is it doesn't seem to do that at all. The idea would be that deleting a project would also delete all the versions, which would delete all the links and clear their cached versions, but using $this->versions()->delete(); doesn't seem to actually call the delete() method in the Version model.
Any ideas on how to get this working as I expect it to?
class Project extends Eloquent {
protected $softDelete = true;
public function versions()
{
return $this->hasMany('Version');
}
public function delete()
{
$this->versions()->delete();
return parent::delete();
}
}
class Version extends Eloquent {
protected $softDelete = true;
public function project()
{
return $this->belongsTo('Project', 'project_id');
}
public function links()
{
return $this->hasMany('Link');
}
public function delete()
{
$this->links()->delete();
return parent::delete();
}
}
class Link extends Eloquent {
protected $softDelete = true;
public function version()
{
return $this->belongsTo('Version', 'version_id');
}
public function delete()
{
Cache::forget($this->id);
return parent::delete();
}
}
You want to use the models events instead of overriding the core functions. To quote the docs:
Eloquent models fire several events, allowing you to hook into various points in the model's lifecycle using the following methods: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
What you want to do is hook into these. You can do this a couple of ways (see the docs here). Here's an example by setting a model boot method:
public static function boot()
{
parent::boot();
/**
* Deleting
* - Called before delete()
* - Passes the instance in as param
*/
static::deleting(function($project){
// Get the project versions ids as an array
$ids = $project->versions()->lists('id');
// Delete the versions
Version::whereIn('id', $ids)->delete();
});
}
You can then do the same in your other models. Hope that helps!
$this->versions()->delete(); as well as Version::whereIn('id', $ids)->delete(); does a delete() call on a query builder, not the eloquent models, which means model events don't get fired. Instead, you should do:
foreach($this->versions as $version) { $version->delete(); }
I'm a real newbie to Laravel but I'm loving it so far. I'm struggling on thing however, I want to retrieve the data for the user that is logged in and I am not sure how to go about this.
I have a few tables but I'll keep it basic for now, I have projects table and a users table, I've defined the relationships between these two in the models as so:
user.php
public function projects() {
return hasMany('project');
}
project.php
<?php
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Project extends Eloquent
{
public function user()
{
return belongsTo('user');
}
}
I know I can do the following to retrieve all projects in the database with a foreach loop, however this doesn't retrieve the logged in users projects:
$projects = DB::table('projects')->get();
I saw one tutorial which wasn't very in depth but he said to access the model query I would have to use the following command:
$project = User::all()->projects;
However this hasn't worked either. Can anyone point me into the right direction with real tutorials or post simple examples?
Thanks in advance
Those are the projects of your logged in user:
if (Auth::check())
{
$projects = Auth::user()->projects;
}
And this must be in your relation:
class User extends Eloquent
{
public function projects() {
return this->hasMany('Project');
}
}
You also need to add $this to your Project relation:
class Project extends Eloquent
{
public function user()
{
return $this->belongsTo('User');
}
}