Laravel Eloquent appending a non-relation ship data with the result - php

I have this model file:-
namespace App;
use Illuminate\Database\Eloquent\Model;
class Observation extends Model
{
protected $fillable = ['observation', 'recommendation', 'priority', 'report_asset_id'];
protected $appends = ['facility'];
public function attachments()
{
return $this->hasMany('App\ObservationAttachment');
}
public function report_facility()
{
return $this->belongsTo('App\ReportFacility');
}
public function getFacilityAttribute()
{
return $this->report_facility()->facility;
}
}
And this is my query code:-
$observations = Observation::orderBy('created_at','desc')
->with('attachments')->get();
return response()->json($observations);
I am trying to append getFacilityAttribute to be included in result array .
I tried to use the protected $append model array but got error :-
Call to undefined method
Illuminate\Database\Eloquent\Relations\BelongsTo::facility()

The following line is incorrect:
return $this->report_facility()->facility
You are starting a query, calling the report_facility as a function (report_facility()), returns a query builder object, on which the facility function is unknown.
You should do:
return $this->report_facility->facility
In this case, eloquent will give you the ReportFacility model, from which you can retrieve the facility property or relation.
It's similar to:
return $this->report_facility()->first()->facility

Related

Laravel object value with Blade statement does not work if model has accessor

i have an Laravel object model with accessor:
class NutritionalPlanRow extends Model
{
use HasFactory;
private $nomeAlimento;
public function __construct($aliment = null,
array $attributes = array()) {
parent::__construct($attributes);
if($aliment){
$this->aliment()->associate($aliment);
$this->nomeAlimento = $aliment->nome;
}
}
public function aliment()
{
return $this->belongsTo('App\Models\Aliment');
}
protected $guarded = [];
public function getNomeAlimentoAttribute()
{
return $this->nomeAlimento;
}
}
and i want to print the nomeAlimento value in a Blade page with Blade statement, for example:
.
.
<tbody>
#foreach( $plan->nutritionalPlanRows as $planRow )
<tr>
<td>
{{ $planRow->nomeAlimento}}
</td>
.
.
but the value inside the table cell is not printed, as if $planRow->foodName is null. In reality it is not empty, in fact if I print {{$planRow}} the structure of the object is complete, and all the attributes are set.
I noticed that if in the model I remove the accessor (getNomeAlimentoAttribute()), then the value in the blade page is correctly printed.
Why?
Thanks.
There are a few things that need attention:
First: Why do you need a constructor? You can define a calculated attribute without the constructor
use App\Models\Aliment;
class NutritionalPlanRow extends Model
{
use HasFactory;
public function aliment()
{
return $this->belongsTo(Aliment::class);
}
protected $guarded = [];
public function getNomeAlimentoAttribute()
{
return optional($this->ailment)->nome;
}
}
Second: It seems like a code smell when using constructor in Eloquent Model class to set relations. Ideally relations should be set/associated from within Controller.
Third: I feel declaring $nomeAlimento as private property on the class is not required. In Laravel calculated properties/attributes can be provided with accessors.
Update:
class Patient extends Model
{
use HasFactory;
protected $dates = ['day_born'];
protected $guarded = [];
public function getYearsAttribute(){
Log::info('patient all data '.$this); //Print correct all data
Log::info('Day'.$this->day_born); //print empty
return Carbon::parse($this->day_born)->diffForHumans(now());
}
}
Read https://carbon.nesbot.com/docs/ for more goodies.

Return class variable of a Laravel Model in JSON

I have the following model class
class MyModel extends Model {
public $some_variable; // I don't want to store this in the database
protected $fillable = ['column1', 'column2'];
In the controller:
$model = MyModel::find(2);
$model->some_variable = "some value"; // Dynamically calculated each time
return response()->json($model);
The response contains all the columns from MyModel but does not contain $some_variable. Why could this be happening? Are class variables transient by default?
Model's data is internally kept in $attributes array, so you may want to put it there prior converting your data to JSON:
...
$model->some_variable = ...;
return response()->json($model);
Because you have defined $some_variable on the Model, it will not show up in the array/json output. The array/json output only includes the table data (stored in the $attributes property) and the loaded relationship data.
If you want this field to show up, you can override the toArray() method on the model, or you could create an accessor method and add that to the $appends property.
Override toArray():
class MyModel extends Model
{
public $some_variable;
public function toArray()
{
$data = parent::toArray();
$data['some_variable'] = $this->some_variable;
return $data;
}
}
Use an accessor and $appends:
class MyModel extends Model
{
public $some_variable;
protected $appends = ['some_variable'];
public function getSomeVariableAttribute()
{
return $this->some_variable;
}
}
You can read about accessors here. You can read about appending data to json here.

Laravel Eloquent ORM firstOrNew mass assignment exception

I'm trying to look up a model in my database based on 2 fields, and if it doesn't exist, create a new model which contains those two values. I'm attempting to use the firstOrNew method to achieve this:
$store = Store::firstOrNew(array('ext_hash' => $ext_hash, 'ext_type_id' => EXT_TYPE_ID));
However, this code is throwing a MassAssignmentException.
Is the only way to avoid this exception to assign fillable properties on the class level? According to the documentation, I should be able to assign fillable properties on the instance level, rather than for the entire class, but how would I do that?
Here's the code for the Store model:
<?php
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Store extends Eloquent{
use SoftDeletingTrait;
public function products(){
return $this->hasMany('Product');
}
public function faqs(){
return $this->hasMany('ProductFaq');
}
public function customer_questions(){
return $this->hasMany('CustomerQuestion');
}
public function users(){
return $this->hasMany('User');
}
}
fillable() is the method you need:
$search = array('ext_hash' => $ext_hash, 'ext_type_id' => EXT_TYPE_ID);
$store = (Store::where($search)->first())
?: with(new Store)->fillable(array_keys($search))->fill($search);
or:
$store = new Store;
$store = ($store->where($search)->first()) ?: $store->fillable(array_keys($search))->fill($search);

Laravel get related models of related models

I have a RepairRequest model, which is associated with a Vehicle.
class RepairRequest extends \Eloquent {
public function vehicle() {
return $this->belongsTo('Vehicle');
}
}
class Vehicle extends \Eloquent {
public function requests() {
return $this->hasMany('RepairRequest');
}
}
I would like to get all RepairRequests for the vehicle associated with a given RepairRequest, so I do
return RepairRequests::find($id)->vehicle->requests;
This works fine.
However, RepairRequests have RepairItems:
// RepairRequest class
public function repairItems() {
return $this->hasMany('RepairItem', 'request_id');
}
// RepairItem class
public function request() {
return $this->belongsTo('RepairRequest', 'request_id');
}
which I would like to return too, so I do
return RepairRequests::find($id)->vehicle->requests->with('repairItems');
but I get the following exception:
Call to undefined method Illuminate\Database\Eloquent\Collection::with()
How can I write this so that the returned json includes the RepairItems in the RepairRequest json?
Load related models using load method on the Collection:
return RepairRequests::find($id)->vehicle->requests->load('repairItems');
which is basically the same as:
$repairRequest = RepairRequests::with('vehicle.requests.repairItems')->find($id);
return $repairRequest->vehicle->requests;
I'd suggest eager loading everything.
return RepairRequests::with('vehicle.requests.repaireItems')->find($id);

Get array of Eloquent model's relations

I'm trying to get an array of all of my model's associations. I have the following model:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public function author()
{
return $this->belongsTo('Author');
}
public function category()
{
return $this->belongsTo('Category');
}
}
From this model, I'm trying to get the following array of its relations:
array(
'author',
'category'
)
I'm looking for a way to pull this array out from the model automatically.
I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.
What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?
It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship
public function articles() {
return $this->hasMany('Article');
}
When you call this method like:
$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection
Also, as I said with, when you use something like this:
$article = Article::with('author')->get(1);
In this case, the first article (with id 1) will be loaded with it's related model Author and you can use
$article->author->name; // to access the name field from related/loaded author model
So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:
$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model
To convert them to an array you may use toArray() method like:
dd($article->getRelations()->toArray()); // dump and die as array
The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:
public function toArray()
{
$attributes = $this->attributesToArray();
return array_merge($attributes, $this->relationsToArray());
}
It merges model attributes and it's related model's attributes after converting to array then returns it.
use this:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public $relationships = array('Author', 'Category');
public function author() {
return $this->belongsTo('Author');
}
public function category() {
return $this->belongsTo('Category');
}
}
So outside the class you can do something like this:
public function articleWithAllRelationships()
{
$article = new Article;
$relationships = $article->relationships;
$article = $article->with($relationships)->first();
}
GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.
You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.
Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships
You can use the following trait:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
trait EloquentRelationshipTrait
{
/**
* Get eloquent relationships
*
* #return array
*/
public static function getRelationships()
{
$instance = new static;
// Get public methods declared without parameters and non inherited
$class = get_class($instance);
$allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
$methods = array_filter(
$allMethods,
function ($method) use ($class) {
return $method->class === $class
&& !$method->getParameters() // relationships have no parameters
&& $method->getName() !== 'getRelationships'; // prevent infinite recursion
}
);
\DB::beginTransaction();
$relations = [];
foreach ($methods as $method) {
try {
$methodName = $method->getName();
$methodReturn = $instance->$methodName();
if (!$methodReturn instanceof Relation) {
continue;
}
} catch (\Throwable $th) {
continue;
}
$type = (new \ReflectionClass($methodReturn))->getShortName();
$model = get_class($methodReturn->getRelated());
$relations[$methodName] = [$type, $model];
}
\DB::rollBack();
return $relations;
}
}
Then you can implement it in any model.
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;
class User extends Authenticatable
{
use Notifiable, HasApiTokens, EloquentRelationshipTrait;
Finally with (new User)->getRelationships() or User::getRelationships() you will get:
[
"notifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"readNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"unreadNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"clients" => [
"HasMany",
"Laravel\Passport\Client",
],
"tokens" => [
"HasMany",
"Laravel\Passport\Token",
],
]
I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.
Just run (Composer 2.x is required!):
require pablo-merener/eloquent-relationships
If you are on laravel 9, you are able to run artisan command model:show

Categories