Custom attribute crashing on toJSON - laravel

I am trying to determine which position the order is in to generate a order id, but this crashes laravel, nothing in the logs, just a 500 error in the browser:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Load extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $appends = ['order_no'];
public function workorder()
{
return $this->belongsTo('App\WorkOrder', 'work_order_id');
}
public function getOrderNoAttribute()
{
$count = 1;
foreach ($this->workorder->loads as $load) {
if ($load->id == $this->id) {
break;
}
$count++;
}
return $this->workorder->id . "-" . $count;
}
}
When I changed it to return just an integer it worked, so I am almost certain it is the relation access causing the issue. Is there a way to do this that is better?

Generally while defining calculated attributes, dependence on relationship should be avoided. So one way to achieve what you are trying is (as you mentioned solved) is to get all loads having the same work_orderid and proceed with it.
public function getLoadCountAttribute ()
{
$id = $this->work_order_id;
return static::where('work_order_id', $id)->count();
}
Another way, more logical I guess, would be to define a relationship and eager load
//define a relation in your Load model
public function load_count ()
{
return count($this->workorder->loads)
//-1 if you want to exclude the current load from count
;
}
//Then use Load::with('load_count') to eager load the load_count
//You may also use global scope
Yet another way would be to define a static function on Workorder model, which will accept an id and return the load_count
//Workorder model
public static function getLoadCount($id)
{
$workorder = static::findOrFail($id);
return count($workorder->loads);
}
Hope this helps.

Related

nested hasmany relationship in laraval-admin

I am stuck in a nested hasmany relation. Here is a scenario where there are no units entered separately by the user and milestones in this form must be equal to the number of units and each unit can have multiple milestones. I was trying to loop but it is not working.
for($i = 1; $i <= $noOfUnit; $i++){
$form->hasmany('majorMilestone', 'Unit '.$i , function (Form\NestedForm $form) use($visitId, $projectId, $ps2EditId, $i) {
$form->hidden('project_id', 'ProjectId')->default($projectId);
$form->text('major_milestone', "Major Milestone")->prepend(false);
$form->date('scheduled', "Scheduled");
})->setWidth(9, 3);
}
more clarification
for example I have a text input on page one, where user add number of units. On second page I need to capture the completion date an remark in steps for each unit. Then I have looped the number of unit to get the below structure
and I have below model in place to add/modify data
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Step2 extends Model
{
protected $table = 'proj_clearence_bckwrd_frwrd_supply';
public $timestamps = false;
public function majormilestone1()
{
return $this->hasMany(Majormilestone::class,'edit_id')->where('unit_no', '1');
}
public function majormilestone2()
{
return $this->hasMany(Majormilestone::class,'edit_id')->where('unit_no', '2');
}
public function majormilestone3()
{
return $this->hasMany(Majormilestone::class,'edit_id')->where('unit_no', '3');
}
}
but this code is limited to three units only. I need to remove this dependency.
I hope this is clear now.
here is the code for MajorMilestone model.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
class Majormilestone extends Model
{
protected $table = 'major_milestone';
protected $fillable = ['project_id', 'edit_id', 'visit_id', 'unit_no', 'major_milestone', 'scheduled', 'anticipated', 'actual', 'remarks'];
public $timestamps = false;
public function step2()
{
return $this->belongsTo(Step2::class,'edit_id');
}
}

Laravel exception: Creating default object from empty value

I'm getting this error just by instantiating a Model and setting a property.
$order_datail = new OrderDetail;
$order_detail->quantity = $product['quantity'];
I'm searching for hours for the cause of this problem but can't find it.
The constructor of OrderDetail is executed. table is order_details, but even by setting protected $table = 'order_details', I'm still getting this error. And yes there is a column 'quantity' in this table.
Strange thing is that I've no problem with other models.
$order = new Order;
$order->pickup_date = $request['date'];
$order->pickup_time = $request['pickupTime'];
The above code runs fine.
Model OrderDetail:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class OrderDetail extends Model
{
use SoftDeletes;
public function replaceds()
{
return $this->hasMany('App\Models\Replaced');
}
public function orders()
{
return $this->belongsTo('App\Models\Order');
}
public function products()
{
return $this->hasMany('App\Models\Product');
}
public function collis()
{
return $this->hasMany('App\Models\Colli');
}
}
Anyone that knows what can be the cause of this?
There is a spelling error on $order_datail = new OrderDetail;
Change it to: $order_detail = new OrderDetail;

Eloquent's fillable not working with mutators

I have a following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class PaymentOption extends Model
{
protected $table = 'payment_option';
public $timestamps = false;
protected $fillable = ['payment_option_code', 'payment_option_name'];
public function setCodeAttribute($value)
{
$this->attributes['payment_option_code'] = $value;
}
public function getCodeAttribute()
{
return $this->payment_option_code;
}
public function setNameAttribute($value)
{
$this->attributes['payment_option_name'] = $value;
}
public function getNameAttribute($value)
{
$this->payment_option_name;
}
}
As you can see, I have weird column names. I need mutators cause I will be accepting variables without the prefix payment_option.
Now, I do it in my controller like so:
<?php
namespace App\Http\Controllers;
use App\PaymentOption;
use App\Http\Requests\PaymentOptionRequest;
class PaymentOptionController extends Controller
{
private $paymentOption;
public function __construct(PaymentOption $paymentOption)
{
$this->paymentOption = $paymentOption;
}
public function create(PaymentOptionRequest $request)
{
$paymentOption = $this->paymentOption->fill($request->validated());
dump($paymentOption);
return response()->json([]);
}
}
When I tried to use the fill() it doesn't call the mutators. But when I tried to set it 1 by 1 it worked like so:
$paymentOption->code = $validated['code'];
Why is that so?
Thanks!
fill does loop on the input data that are in your $fillable array
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key, get_class($this)
));
}
}
return $this;
}
hence, you need to add 'code' and 'name' to the fillable array to have them assigned by fill
With latest realese of laravel 8 there is a change in implementation of fill. In laravel older version fill method does work for mutator and database column both. But in laravel 8 implementation of fill method is changed now fill also check if the key given in an array to fill method is a database column or not.
For example if you have database column name user_id and mutator name is user it will not work with laravel 8. It does work for the older version of laravel.
If the $guarded array contains at least one column/key, then that Eloquent Model goes into a strict mode where it can only be filled with values for valid database columns. here is the test is written in the official codebase for that(link to test).
To solve the problem use forceFill. That will solve your problem.

Laravel Mystery - Two Similar Item Types Producing 2 Different Query Strings in Same Use Case

Ok, this is weird... You ready?
I have an item type on my site, lets call it SomeItem
It can have tags associated with it via a one-to-many relationship.
The sorts of queries that Laravel builds when dealing with tags for SomeItem are like this, for instance in response to route api/someitem/10:
select `tags`.*, `someitem_tag`.`someitem_id` as `pivot_someitem_id`, `someitem_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `someitem_tag` on `tags`.`id` = `someitem_tag`.`tag_id` where `someitem_tag`.`someitem_id` in (10)
When I create a second Item with identical settings - let's call it AnotherItems - it treats the database query for extracting tags in a different manner, using a different syntax in the queries. Extremely weird.
(and yes, I have an s at the end of the model name...)
For instance, this route api/anotheritems/1
produces this error:
Base table or view not found: 1146 Table 'mysite.tag_anotheritems' doesn't exist (SQL: select `tags`.*, `tag_anotheritems`.`anotheritems_id` as `pivot_anotheritems_id`, `tag_anotheritems`.`tag_id` as `pivot_tag_id` from `tags` inner join `tag_anotheritems` on `tags`.`id` = `tag_anotheritems`.`tag_id` where `tag_anotheritems`.`anotheritems_id` in (1))
See what is happening? Of course I am getting this error - in the database this tag table for AnotherItems is created as anotheritems_tag. That is analogous to SomeItem.
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item??? WTF?
First let me show you how SomeItem is set up.
Here is the database structure related to Tags:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSomeItemTagTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('someitem_tag', function (Blueprint $table) {
$table->integer('tag_id')->unsigned();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->integer('someitem_id')->unsigned();
$table->foreign('someitem_id')->references('id')->on('someitems')->onDelete('cascade');
$table->primary(array('tag_id', 'someitem_id'));
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('someitem_tag');
}
}
There is a Tags model/class that has this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $fillable = ['name'];
protected $hidden = [];
public $timestamps = false;
public function someitems()
{
return $this->belongsToMany(SomeItem::class);
}
}
And here is some relevant lines for SomeItem model/class:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use App\Presenters\Presentable;
use Illuminate\Notifications\Notifiable;
use Auth;
class Exercise extends Model
implements Presentable
{
use Traits\SerializesUniversalDate;
use Traits\Presents;
use Notifiable;
protected $presenter = 'App\Presenters\SomeItemPresenter';
protected $fillable = ['title', etc];
protected $hidden = [];
public function parentitem()
{
return $this->belongsTo(ParentItem::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
/**
* Update lesson tag array.
*
* #param array \App\Tag $tags
* #return void
*/
public function updateTags($tagsArray)
{
foreach ($tagsArray as &$value)
{
$tag = Tag::where('name', $value['name'])->first();
if (is_null($tag))
{
$tag = new Tag([
'name' => $value['name']
]);
$tag->save();
}
if (!$this->tags->contains($tag->id))
{
$this->tags()->attach($tag->id);
}
}
foreach($this->tags as &$existingTag)
{
if (!self::arrayContains($tagsArray, 'name', $existingTag->name))
{
$this->tags()->detach($existingTag->id);
}
}
$this->load('tags');
}
private static function arrayContains($array, $key, $value)
{
foreach ($array as $item)
{
if($item[$key] == $value) return true;
}
return false;
}
}
And here is some relevant code for SomeItem API controller:
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Input;
class SomeItemController extends Controller
{
public function index(Request $request)
{
$query = \App\SomeItem::query();
return $query->get()->load('parentitem')->load('tags');
}
//show item for editing
public function show($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->load('parentitem')->load('tags');
$someitem->attachKindToFiles();
return $someitem;
}
//store new entry to db
public function store()
{
$someitem = \App\SomeItem::create(Input::all());
isset(Input::all()['tags']) ? $someitem->updateTags(Input::all()['tags']) : '';
return $someitem;
}
//update/save
public function update($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->update(Input::all());
$someitem->updateTags(Input::all()['tags']);
$someitem->load('tags');
return $someitem;
}
There is also a SomeItem presenter and composer but they don't do anything with tags.
With AnotherItems, I literally I duplicated everything from SomeItem and just changed names as needed.
So in the Tag model there is
public function anotheritems()
{
return $this->belongsToMany(AnotherItems::class);
}
In AnotherItems model there is this, for instance
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In the AnotherItems API controller there is this, for instance (which is for route api/anotheritems/1):
public function index(Request $request)
{
$query = \App\AnotherItems::query();
if ($request->has('id')) {
$query->where('id', $request['id']);
}
return $query->get()->load('parentitem')->load('tags');
}
So, this is a total mystery. I have been trying to figure this out for 2 days now. And I continue asking myself
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item???
I upgraded from laravel 5.2 to 5.3 and it is after the upgrade that I added this AnotherItems. But I can't figure out how that could possibly alter things in terms of these database queries.
I have tried a ton of artisan commands for clearing everything imaginable, but somewhere in the framework it wants to handle SomeItem and AnotherItems differently when building these join queries to extract/save tags.
Thoughts?
thanks,
Brian
Decided to step through code in debugger. Seems things are breaking down in Str.php in various snake related function, and I also noticed a snakeCache call, whatever the heck that is. Not sure why such a strange methodology to determine table names... Also in these functions there is some pluralizing related checks, so maybe this is related to me using an s at the end of my item name. Pretty messed up stuff if an s at the end of a model name can cause two different logic branches...

How can you use Eloquent to find related values two tables away and in a different database?

I am developing a system that extends an existing ERP system, and so is accessing two databases (both on the same MS SQL Server). I am trying to access items on the "Equipment" model (this is a table in the ERP database) through the "EquipmentInstance" model from the "EquipmentType" model (these two are in the new database). They are related as per this diagram:
The three models are as follows:
EquipmentType
namespace App;
use Illuminate\Database\Eloquent\Model;
class EquipmentType extends Model
{
protected $table = 'dbo.EquipmentType';
protected $connection = 'sqlsrv';
protected $primaryKey = 'EquipmentTypeID';
protected $fillable = [
'TypeName',
'ProductManager'
];
public function EquipmentInstance()
{
return $this->hasMany(EquipmentInstance::class,'EquipmentTypeID', 'EquipmentTypeID');
}
public function Equipment()
{
return $this->hasManyThrough(
Equipment::class,
EquipmentInstance::class,
'TypeID',
'PartNum',
'TypeID',
'PartNum'
);
}
}
EquipmentInstance
namespace App;
use Illuminate\Database\Eloquent\Model;
class EquipmentInstance extends Model
{
protected $table = 'dbo.EquipmentInstance';
protected $primaryKey = 'EquipmentID';
protected $keyType = 'string';
protected $connection = 'sqlsrv';
protected $fillable = [
'EquipmentID',
'EquipmentTypeID',
'PartNum'
];
public function Part()
{
return $this->belongsTo(Part::class,'PartNum','PartNum');
}
public function Equipment()
{
return $this->hasMany(Equipment::class,'PartNum', 'PartNum');
}
public function EquipmentType()
{
return $this->belongsTo(EquipmentType::class); /*,'EquipmentTypeID', 'EquipmentTypeID'*/
}
/* public function Attribute()
{
return $this->belongsTo(Equipment::class,'SerialNumber', 'JobNum');
}
public function TechNote()
{
return $this->belongsTo(Equipment::class,'SerialNumber', 'JobNum');
}*/
}
Equipment
namespace App;
use Illuminate\Database\Eloquent\Model;
class Equipment extends Model
{
protected $table = 'ERP.SerialNo';
public $timestamps = false;
protected $primaryKey = 'SerialNumber';
protected $keyType = 'string';
protected $connection = 'epicor';
public function Part()
{
return $this->belongsTo(Part::class,'PartNum','PartNum');
}
public function Customer()
{
return $this->belongsTo(Customer::class,'CustNum', 'CustNum');
}
public function Equipment()
{
return $this->belongsTo(Equipment::class,'SerialNumber', 'JobNum');
}
public function EquipmentInstance()
{
return $this->belongsTo(EquipmentInstance::class,'PartNum', 'PartNum');
}
}
On the EquipmentType Controller I am trying to get all of the Equipment through the EquipmentInstance so for each EquipmentInstance I can display all of the Equipments.
EquipmentType Controller
public function show(EquipmentType $EquipmentType)
{
$EquipmentInstance = $EquipmentType->EquipmentInstance()
->get();
$Equipments = $EquipmentType->EquipmentInstance()->Equipment()
->get();
return view('EquipmentType.show', compact('EquipmentType', 'EquipmentInstance', 'Equipments'));
}
The error message I get is
"BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::Equipment()"
I believe the issue is that (my understanding is rocky) that Eloquent is trying to write one query to access both databases, which is failing. However I am not sure how to proceed.
Any help would be greatly received.
Richard
Update
I have implemented what gbalduzzi suggested in his answer, which almost worked, and I am sure the issue is with my blade implemtention. I have nested two forloops:
#foreach($EquipmentType->EquipmentInstance as $EquipmentInstance)
#foreach($Equipments as $Equipment)
<tr>
<td>{{$EquipmentInstance->PartNum}} - {{$EquipmentInstance->Part->PartDescription}}</td>
<td>{{$Equipment->SerialNumber}}</td>
<td>{{$Equipment->SNStatus}}</td>
<td>{{--{{$Equipment->Customer->LegalName}}--}}</td>
</tr>
#endforeach
#endforeach
Which is displaying the serial numbers (from the Equipment model) for the first EquipmentInstance only and repeating them for all EquipmentInstanced.
Update 2
I have proven that the issue is with first() in the suggested answer, as if I change this to last() the results change as you would expect (see update 1). So my question now is:
Is there an equivelant of first(), last() which is all() or every()?
The problem is not in your database configuration, but in the way you are calling the relationship. Instead of:
$Equipments = $EquipmentType->EquipmentInstance()->Equipment()
->get();
use:
$Equipments = $EquipmentType->EquipmentInstance->first()->Equipment()
->get();
Long answer
In Eloquent, you can use a relationship in 2 ways:
as a magic field (i.e. $EquipmentType->EquipmentInstance). In this case you get as a result an instance of the model EquipmentInstance (also, if you already queried it, it directly returns the value without executing a new query)
as an eloquent query (i.e. $EquipmentType->EquipmentInstance()). Using it as a function, you don't get the model but a RelationShip instance, that is basically an eloquent query and can be chained with other eloquent methods, such as where, orderBy, ecc
So, if you call $EquipmentType->EquipmentInstance()->Equipment() it throws an error because the eloquent query does NOT have the relationship Equipment().
On the other hand, $EquipmentType->EquipmentInstance->Equipment works because it calls Equipment on the actual model instance, that has the Equipment relationship properly defined.

Resources