Model object load/access relation model in controller - php

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.

Related

Laravel transitive relation fails

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

How to pass parameter to a laravel elequent model's event observer

I have a model in laravel and I want to do something after the first time which an object of my model is created. the simplest way is to add a static boot method inside my model's class like the code below:
class modelName extends Model
{
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
});
}
}
so far so good! the problem is: the ONLY parameter that created method accepts is the model object itself(according to the documentation) :
Each of these methods receives the model as their only argument.
https://laravel.com/docs/5.5/eloquent#events
I need more arguments to work with after model creation. how can I do that?
Or is there any other way to do something while it's guaranteed that the model has been created?
laravel version is 5.5.
You're close. What I would probably do would be to dispatch an event right after you actually create the model in your controller. Something like this.
class WhateverController
{
public function create()
{
$model = Whatever::create($request->all());
$anotherModel = Another::findOrFail($request->another_id);
if (!$model) {
// The model was not created.
return response()->json(null, 500);
}
event(new WhateverEvent($model, $anotherModel));
}
}
I solved the issue using static property in eloquent model class:
class modelName extends Model
{
public static $extraArguments;
public function __construct(array $attributes = [],$data = [])
{
parent::__construct($attributes);
self::$extraArguments = $data ;
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
self::$extraArguments; // is available in here
});
}
}
It works! but I don't know if it may cause any other misbehavior in the application.
Using laravel events is also a better and cleaner way to do that in SOME cases.but the problem with event solution is you can't know if the model has been created for sure and it's time to call the event or it's still in creating status ( and not created status).

Cast To Inherited Class via Eloquent Realtionship

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.

Saving object in a class variable and using it in another function - php, laravel

So here's the code
use App\Video;
class HomeController extends Controller
{
protected $video;
public function index()
{
// $video_to_watch is fetched from db and I want to save it and use it in
// another function in this controller
$this -> video = $video_to_watch;
return view('home', compact('video_to_watch'));
}
public function feedback(Request $request)
{
dd($this -> video);
}
}
feedback returns null for some reason.
when I put the
dd($this -> video);
in index() it works fine, not null.
I have tried what's suggested here: Laravel doesn't remember class variables
but it didn't help.
I'm sure it's something stupid I'm overlooking. But can't seem to figure out what, any help much appreciated.
You can't keep your $video value between 2 different requests. You have to fetch your video data in each request.
use App\Video;
class HomeController extends Controller
{
public function index() {
$myVideo = $this->getMyVideo();
return view('home', $myVideo);
}
public function feedback(Request $request) {
dd($this->getMyVideo);
}
private function getMyVideo() {
// fetch $video_to_watch from db
return $video_to_watch ;
}
}
First of all don't fetch data inside a Controller. It's only 'a glue' between model and view. Repeat. No fetching inside a controller.
Use domain services and dependency injection to get business data and if you want to share this data create shared service (single instance).
-
Putting a data object into a controller property class makes a temporary dependency between method calls. Avoid it. Use services instead.

Laravel 5 Eager Loading not working

I look at many search results with this trouble but i can`t get it to work.
The User Model:
<?php namespace Module\Core\Models;
class User extends Model {
(...)
protected function Person() {
return $this->belongsTo( 'Module\Core\Models\Person', 'person_id' );
}
(...)
And the Person Model:
<?php namespace Module\Core\Models;
class Person extends Model {
(...)
protected function User(){
return $this->hasOne('Module\Core\Models\User', 'person_id');
}
(...)
Now, if i use User::find(1)->Person->first_name its work. I can get the Persons relations from the User Model.
But.. User::with('Person')->get() fails with a Call to undefined method Illuminate\Database\Query\Builder::Person()
What im doing wrong? i need a collection of all the users with their Person information.
You have to declare the relationship methods as public.
Why is that? Let's take a look at the with() method:
public static function with($relations)
{
if (is_string($relations)) $relations = func_get_args();
$instance = new static;
return $instance->newQuery()->with($relations);
}
Since the method is called from a static context it can't just call $this->Person(). Instead it creates a new instance of the model and creates a query builder instance and calls with on that and so on. In the end the relationship method has to be accessible from outside the model. That's why the visibility needs to be public.

Categories