I have an app with following models and relations:
Contact morphs many Values:
public function customFieldValues()
{
return $this->morphMany(Value::class, 'model');
}
Value belongs to CustomField:
public function customField(): BelongsTo
{
return $this->belongsTo(CustomField::class);
}
CustomField belongs to Validation:
public function validation(): BelongsTo
{
return $this->belongsTo(Validation::class);
}
I wanted to add a shorthand for value validation, so I added the following to Value class:
public function validation(): BelongsTo
{
return $this->customField->validation();
}
The issue I'm having is when I try to eager load values with validation on a Contact model.
Doing Contact::with('customFieldValues.validation')->first() fails:
Call to a member function validation() on null
(the null here refers to $this->customField in Value class validation() method)
When using the "full" relation without the shorthand, it returns correctly. So Contact::with('customFieldValues.customField.validation')->first() works.
Also when going from perspective of Value, it works fine: Value::first()->validation.
I used this type of relations before, does it have something to do with polymorphic relations? Can I make this work or is it impossible?
It should works with this :
public function validation(): BelongsTo
{
return $this->getAttribute('customField')->validation();
}
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
Thought I'd ask this as Laravel is the most elegant Framework I've come across and wondered if there was a "prettier way" of doing this.
I have a system which records books such that:
class Chapter extends Model
{
public function book()
{
return $this->belongsTo('\App\Book');
}
}
In the system there are number of other models which extend from "Book" such as "Novel", "Biography" etc. Is there a way for Eloquent to provide me with a correctly cast object given the right info (i.e. a namespaced class)? Currently, I am obtaining the book and the casting it using the function at https://gist.github.com/borzilleri/960035 which works but doesn't feel very "tidy".
I can see a few different options here. One would be to write your class like this:
class Chapter extends Model
{
public function book()
{
return $this->belongsTo('\App\Book');
}
public function biography()
{
return $this->belongsTo('\App\Biography')->where('type', 'biography');
}
public function novel()
{
return $this->belongsTo('\App\Novel')->where('type', 'novel');
}
}
You'd then need to know ahead of time which type of book it is though. Another would be to do something like this:
class Chapter extends Model
{
protected function parent_book()
{
return $this->belongsTo('\App\Book');
}
public function getBookAttribute()
{
$book = $this->parent_book;
if (!$book) return $book; // No related book.
if ($book->type == 'novel') return (Novel)$book;
if ($book->type == 'biography') return (Biography)$book;
return $book;
}
}
You still have to do all of the casting yourself, but at least it's all in one place and transparent to the rest of the app, as it can still just reference $chapter->book For this second solution, if you ever set $chapter->book = new Book(), you'd also need to make sure to make a setBookAttribute() function.
One more complicated possibility would be to create your own custom relationship type by extending the BelongsTo class and overriding getResults() to to the casting before returning the result. This would be pretty transparent from the outside and would let you still call $chapter->book() and treat it as a relationship.
This should be attributed to Joshua Dwire as he set me on the path to this solution. I was intrigued by his reference to extending the standard BelongsTo class and make it work for me. Ideally I want to be able to call a custom relationship:
$this->belongsToBook('\App\Book');
And for that function to return a correctly cast object.
Routing through the code I found that it was the trait HasRelationship used by Model which was responsible for returning the relationship. By changing that relationship we can change the implementation and therefore the returned object.
I also wanted to replicate the same methodology that Laravel employs so have mimiced it in my own app.
With all that in mind the first step is to create a new trait HasBookRelationship which can be used in a model to handle the call to $this->belongsToBook('\App\Book'):
trait HasBookRelationship
{
public function belongsToBook($related, $foreignKey = null, $ownerKey = null, $relation = null)
{
if (is_null($relation)) {
$relation = $this->guessBelongsToRelation();
}
$instance = $this->newRelatedInstance($related);
if (is_null($foreignKey)) {
$foreignKey = \Str::snake($relation).'_'.$instance->getKeyName();
}
$ownerKey = $ownerKey ?: $instance->getKeyName();
//We change the return relationship here
**return new BelongsToBook(
$instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
);**
}
}
This is simply copied from the existing belongsTo method in the HasRelationships trait. The key thing here is that we are going to return a custom relationship BelongsToBook and use that to override what is returned. The last line of the method is changed to return our desired relationship class.
The class we use is extended from BelongsTo but we change the get method to cast the object before returning it.
class BelongsToBook extends BelongsTo
{
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
{
parent::__construct($query, $child, $foreignKey, $ownerKey, $relationName);
}
public function get($columns = ['*'])
{
$objs = $this->query->get($columns);
//iterate over the collated objects...
$objs->transform(function($item)
{
//..and return a cast object with whatever method you want
return castTheCorrectObject($item);
});
return $objs;
}
}
castTheCorrectObject can be any casting function you like perhaps set up as a helper or another method in the relationship.
Once these are set up, we can empoy it in our own Model:
class Author extends Model
{
use HasBookRelationship;
public function books()
{
return $this->belongsToBook('\App\Book');
}
}
This will return a collection of correctly cast objects and maintains the relationship.
One thing did puzzle me though. The method I overrode in my BelongsToBook class was get() and not getResults() as suggested by Joshua. get() is defined in Relation and is inherited by BelongsTo where as getResults() is defined in BelongsTo. I'm not sure what the difference between getResults() and get() is nor why I had to override get() rather than getResults(). If anyone can shed any light , it would be appreciated.
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');
}
Using laravel I have a method that I am posting to in my Controller:
public function hired(Quote $quote)
{
var_dump($quote->project);
exit;
$quote is created as a Model object but the above code returns NULL
I have the correct relationship setup:
public function project()
{
return $this->belongsTo(Project::class);
}
I understand that I need to "load" these relationship models onto the object but not sure how??
I thought that when I access ->project the relationship would be automatically loaded...
Thanks
Ensure that you maintain the same naming convention for the variable in your route and method arguments.
Example:
Route::post('quote/{quote_id}/hired', 'QuoteController#hired')->name('quote.hired');
Should then be in the controller:
public function hired(Quote $quote_id) {
Thanks to #scottevans93 for leading me to my mistake.
I have a model called Book for a table called books, that has a field called cover_image.
However, I want to define an accessor for the field cover_image instead of just retrieving the default value.
This is how I attempted to do this:
class Book extends Model {
public function getCoverImageAttribute() {
if ($this->cover_image === null) { // Not sure how to check current value?
return "a.jpg"
}
return $this->cover_image;
}
}
However the above of course does not work because calling $this->cover_image again would cause recursion
How can I fix this?
You must check for the attribute instead:
class Book extends Model {
public function getCoverImageAttribute() {
return $this->attributes['cover_image'] ?? "a.jpg";
}
}
This will allow you to normally use $book->cover_image as well.
I believe the following example will also work:
class Book extends Model {
public function getCoverImageAttribute($value) {
return is_null($value) ? 'a.jpg' : $value;
}
}