Many to Many form binding - laravel

How to binding a input with many to many relationship data?
My relationship is: a Model has many Damages, and a Damage has many Models. in pivot table exists a price field.
I need populate a input with price data.
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]") }}
My Model:
class Model extends \BaseModel {
public function damages()
{
return $this->belongsToMany('Damage', 'prices_damages', 'model_id', 'damage_id')
->withPivot('price')
->withTimestamps();
}
}
Pivot table
Schema::create('prices_damages', function(Blueprint $table)
{
$table->increments('id');
$table->integer('model_id')->unsigned();
$table->integer('damage_id')->unsigned();
$table->float('price')->nullable();
$table->timestamps();
});
Controller
/**
* Display a index dashboard page.
*
* #return \Illuminate\Http\Response
*/
public function getDamages()
{
$models = \Model::orderBy('order')->get();
$damages = \Damage::orderBy('order')->get();
return $this->render('Avarias', 'prices.damages', compact('models', 'damages'));
}
View:
<table class="table-striped table-header-rotated">
<thead>
<tr>
<th></th>
#foreach ($damages as $damage)
<th class="vertical"><div><span>{{ $damage->name }}</span></div></th>
#endforeach
</tr>
</thead>
<tbody>
#foreach ($models as $model)
<tr>
<td>{{ $model->fullname }}</td>
#foreach ($damages as $damage)
<td>
{{ Form::input('number', "prices[{$model->id}][{$damage->id}]", null, ['min' => 0, 'step' => 0.01]) }}
</td>
#endforeach
</tr>
#endforeach
</tbody>
</table>

You can't bind a collection (in the sense of laravel form model binding), so you can do this:
#foreach ($model->damages as $damage)
{{ Form::input('number', "damages[{$damage->id}][price]", $damage->pivot->price) }}
#endforeach

Related

How to handle missing records of a Product's attribute value in laravel 7

Background Information:
I'm learning laravel 7 by creating an inventory system.
I have several models: Product, Stock, and Attribute.
The Product is like a group that holds all of the stock. Because I can have a Product like a speaker. But I can have many quantities of that same product that have different attribute values. This is when the Stock becomes important.
The Stock is where I have all of the items in my inventory.
For example: If I have 20 speakers, there will be one record in the products table and 20 records in the stocks table.
The Attribute is because I want to track different attributes depending on the Product. But the value of those attributes doesn't belong to the product; It belongs to the Stock of the product.
This is how my models relate:
A Product hasMany Stock
A Stock belongsTo a Product
A Product hasMany Attribute
An Attribute belongsTo a Product
An Attribute belongsToMany Stock
A Stock belongsToMany Attribute (I'm not very confident about this one.)
Goal:
The Product is L-Acoustics LA12X
The product's Attributes are Serial Number and Firmware
The product's Stock is the card that says "Stock Level".
For testing and simplicity, I copied the same values of Serial Number and Firmware to all of the Product's Stock; but in real life they should all have different serial numbers and firmware versions.
Problem:
I only get the output from the picture above when there is a value for each attribute. For example: if I delete the attribute value of the Serial Number from the first stock I get this.
Do you see how it shifted the Firmware value to the Serial Numbers column?
In the case that the record is deleted or the value hasn't been created I would like to put an empty cell in that table so that it looks like this. (I created this image by editing the HTML of the page, I still don't know how to get this output)
What I have tried:
I quickly thought of creating empty cells in my database belonging to the attribute so that when I loop through my records I get a perfect table. But this will quickly become a problem when my database increases size.
You could think of why would there be records that don't exist? Well one example is that my inventory is so old that there are some Serial Numbers that I can't retrieve. Therefore, there will be no records in the table.
Showing some code:
This is my database design:
This is my Product Model:
class Product extends Model {
protected $guarded = [];
protected $casts = [
'brand_id' => 'integer',
'category_id' => 'integer',
'price' => 'decimal:2',
'rental_price' => 'decimal:2',
'active' => 'boolean'
];
public function setNameAttribute($value)
{
$this->attributes['name'] = $value;
$this->attributes['slug'] = Str::slug($value);
}
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function stocks()
{
return $this->hasMany(Stock::class)->orderBy('barcode', 'asc');
}
public function attributes()
{
return $this->hasMany(Attribute::class);
}
}
This is my Stock Model:
class Stock extends Model {
protected $guarded = [];
protected $casts = [
'quantity' => 'integer',
'product_id' => 'integer',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function attributes()
{
return $this->belongsToMany(Attribute::class)->withPivot('value');
}
}
This is my Attribute Model:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
// This method is empty because I don't know how to relate the attribute to its values.
}
I know I have dumped a lot of information but I really need help. I have an open mind and I'm open to new implementations.
UPDATE
Here is the code of my view:
<div class="block-content block-content-full">
<div class="table-responsive">
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($stock->attributes as $attribute)
<td class="text-center">{{ $attribute->pivot->value }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
</div>
</div>
Here is the show method in my controller:
public function show(Product $product)
{
return view('products.show', compact('product'));
}
UPDATE 2:
I updated my Attribute model to this:
class Attribute extends Model {
protected $guarded = [];
protected $casts = ['product_id' => 'integer'];
public function getValueByBarcode($stock)
{
return $this->values()->where('barcode', $stock)->first()->value ?? '';
}
public function product()
{
return $this->belongsTo(Product::class);
}
public function stocks()
{
return $this->belongsToMany(Stock::class);
}
public function values()
{
return $this->belongsToMany(Stock::class)->select('value');
}
}
and in my blade I got the values like this:
<table class="table table-borderless table-striped table-vcenter font-size-sm">
<thead>
<tr>
<th class="text-left">Barcode</th>
#foreach($product->attributes as $attribute)
<th class="text-center">{{ $attribute->name }}</th>
#endforeach
<th class="text-right">Condition</th>
<th class="text-right">Updated</th>
</tr>
</thead>
<tbody>
#forelse($product->stocks as $stock)
<tr>
<td class="text-left">
<strong>{{ $stock->barcode }}</strong>
</td>
#foreach($product->attributes as $attribute)
<td class="text-center">{{ $attribute->getValueByBarcode($stock->barcode) }}</td>
#endforeach
<td class="text-right">
#switch($stock->condition)
#case('Great')
<span class="badge badge-success"><i class="fa fa-check-circle mr-1"></i>Great</span>
#break
#case('Damaged')
<span class="badge badge-warning"><i class="fa fa-exclamation-circle mr-1"></i>Damage</span>
#break
#case('Broken')
<span class="badge badge-danger"><i class="fa fa-times-circle mr-1"></i>Broken</span>
#break
#endswitch
</td>
<td class="text-right">{{ $stock->updated_at->format('M d, Y') }}</td>
</tr>
#empty
<tr><td colspan="100" class="text-center">No Stock Available</td></tr>
#endforelse
</tbody>
</table>
This is the result of that change
I finally managed to get what I wanted but is there a cleaner way of doing the same thing?
I would use keyBy on your collection of stock attributes (Untested)
#php($s_attributes = $stock->attributes->keyBy('attribute_id'))
#foreach($product->attributes as $p_attribute)
<td class="text-center">{{ isset($s_attributes[$p_attribute->id]) ? $s_attributes[$p_attribute->id]->pivot->value : ''}}</td>
#endforeach

Nothing happens when i bind data in my show.blade.php page

When i bind data on my frontend i don't get anything and there is no bug or something like please help. Here the tables structures
class CreateResponesTable
Schema::create('respones', function (Blueprint $table) {
$table->increments('id')->unique();
$table->longText('text_answer')->unique();
$table->string('author_name');
$table->dateTime('lock')->nullable();
$table->timestamps();
$table->softDeletes();
});
the table of relations with respones
class AddRelationshipFieldsToResponesTable
public function up()
{
Schema::table('respones', function (Blueprint $table) {
$table->unsignedInteger('category_id');
$table->foreign('category_id', 'category_fk_851021')->references('id')->on('categories');
$table->unsignedInteger('author_email_id');
$table->foreign('author_email_id', 'author_email_fk_851023')->references('id')->on('users');
$table->unsignedInteger('ask_question_id');
$table->foreign('ask_question_id', 'ask_question_fk_851048')->references('id')->on('ask_questions');
});
}
Here the model of respone
Respone.php
public static function boot()
{
parent::boot();
Respone::Observe(new \App\Observers\ResponeObserver );
}
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
public function author_email()
{
return $this->belongsTo(User::class, 'author_email_id');
}
public function ask_question()
{
return $this->belongsTo(AskQuestion::class, 'ask_question_id');
}
public function notation(){
return $this->belongsToMany(Notation::class)->withPivot('rating');
}
Here the blade file where i bind data of a response
Show.blade.php
<div class="card-body">
<table class="table table-bordered table-striped">
<tbody>
<tr>
<th>
{{ trans('Thématique') }}
</th>
<td>
{{ optional($respone->category)->name }}
</td>
</tr>
<tr>
<th>
{{ trans('Nom auteur') }}
</th>
<td>
#if($respone->author_name)
{{ $respone->author_name }}
#endif
</td>
</tr>
<tr>
<th>
{{ trans('Auteur email') }}
</th>
<td>
{{ optional($respone->author_email)->email }}
</td>
</tr>
<tr>
<th>
{{ trans('Question') }}
</th>
<td>
{{ optional($respone->ask_question)->text_question }}
</td>
</tr>
<tr>
<th>
{{ trans('Réponse') }}
</th>
<td>
{{ $respone->text_answer ?? 'Default' }}
</td>
</tr>
</tbody>
</table>
</div>
here the model of askQuestion
askQuestion.php
public static function boot()
{
parent::boot();
AskQuestion::Observe(new \App\Observers\AskQuestionObserver );
static::addGlobalScope(new CollaborateurScope);
}
/**
* In this method may be it should belongsto instead of hasmany
*/
public function respones()
{
return $this->hasOne(Respone::class, 'ask_question_id', 'id');
}
public function assigned_to_user()
{
return $this->belongsTo(User::class, 'assigned_to_user_id');
}
The backend controller
public function show(Respone $respone)
{ abort_if(Gate::denies('respone_show'), Response::HTTP_FORBIDDEN, '403 Forbidden');
$respone->load('category', 'author_email', 'ask_question');
return view('admin.respones.show', compact('respone'));
}
the frontend controller
/**
* Display the new response done to a question
*
* #return \Illuminate\Http\Response
*/
public function show(Respone $respone, Category $category, User $user,AskQuestion $ask_question)
{
$respone->load('author_email', 'category', 'ask_question');
$ask_question->load('Respones');
return view('respones.show', compact('respone', 'ask_question'));
}

Trying to get property 'name' of non-object. Model relationship not working

I am getting an error message relating to my model relationship and have an identical section of my other app that works fine. I've looked through everything many times and cannot see any reason my model relationship should not work. When I try to access the model relationship in the view, I get "Trying to get property 'name' of non-object"
I have the following in my Controller:
public function edit($id)
{
//
$plansubmission = PlanSubmission::find($id);
$hradmins = PSPlanToHRAdminMapping::where('plan_submission_plan_id', $id)->get();
return view('planbuilder.add-hr-administrators', compact('plansubmission', 'hradmins'));
I have the following in my View:
<h3 class="py-3">Current HR Administrators with Online Access</h3>
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email Address</th>
</tr>
</thead>
<tbody>
#foreach($hradmins as $hradmin)
<tr>
<td> {{$hradmin->hradmin->name }}</td>
<td> {{$hradmin->hradmin->email }}</td>
#endforeach
</tr>
</tbody>
</table>
I have the following in my Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PSPlanToHRAdminMapping extends Model
{
//
protected $guarded = [];
public function hradmin()
{
return $this->belongsTo('App\HRAdmin');
}
}
I have the following Migration:
{
Schema::create('p_s_plan_to_h_r_admin_mappings', function (Blueprint $table) {
$table->unsignedBigInteger('plan_submission_plan_id');
$table->unsignedBigInteger('h_r_admin_id');
$table->timestamps();
$table->foreign('plan_submission_plan_id')->references('id')->on('plan_submissions');
$table->foreign('h_r_admin_id')->references('id')->on('h_r_admins');
});
}
It's possible that for some records you don't have any record for hradmin relationship so you can try adding optional helper like so:
<td> {{optional($hradmin->hradmin)->name }}</td>
<td> {{optional($hradmin->hradmin)->email }}</td>
for example to avoid error

Laravel : get data for user using relationships

Get user's answer , question in page by user id , i want to display table for each user contains his answers with questions
I tried to create show page and add show function in UserController
Controller :
public function show($id)
{
$user = User::find($id);
$user_id = $user->id;
$survey = \App\Survey::pluck('title', 'id')->toArray();
$answers = \App\Answer::where('user_id','=',$user_id)->get();
return view('users.show', compact('user','survey','answers'));
}
view:
<table class="table">
<thead class="thead-light">
<tr>
<th>{{ __('Question') }}</th>
<th>{{ __('Answers') }}</th>
<th>{{ __('Creation Date') }}</th>
</tr>
</thead>
<tbody>
#foreach($answers as $t)
<tr>
<td> {{ optional($t->survey)->title }} </td>
<td> {{ $t->answer }} </td>
<td> {{$t->created_at}} </td>
</tr>
#endforeach
</tbody>
</table>
Answer model :
class Answer extends Model
{
protected $fillable = ['answer','commentaire','user_id','survey_id','last_ip'];
protected $table = 'answer';
public function survey()
{
return $this->belongsTo('App\Survey', 'survey_id');
}
public function question()
{
return $this->belongsTo(Question::class);
}
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
}
user model :
public function questions()
{
return $this->hasMany(Question::class);
}
public function answers()
{
return $this->hasMany(Answer::class);
}
the tables :
answer :
Survey :
I got empty part for question row
You are performing some queries that you actually don't need.
First get rid of this to lines:
$user_id = $user->id; $survey = \App\Survey::pluck('title', 'id')->toArray();
Then change your query to get your answers:
$answers = \App\Answer::where('user_id','=',$user->id)->with(['survey'])->get();
return view('users.show', compact('user','answers'));
Now in your view you could just do this:
<td> {{ $t->survey->title }} </td>
<td> {{ $t->answer }} </td>
<td> {{$t->created_at}} </td>

Laravel view won't loop model even though it is populated

I'm creating a web based interface for my dovecot database.
I can get a list of all the virtual domains in the database and the number of emails and aliases easily enough.
But when I try to load a page to list the emails under a specific domain, it goes weird.
Three simple models:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class VirtualDomain extends Model
{
public function emails()
{
return $this->hasMany('App\VirtualUser', 'domain_id');
}
public function aliases()
{
return $this->hasMany('App\VirtualAlias', 'domain_id');
}
}
class VirtualUser extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
];
}
class VirtualAlias extends Model
{
//
}
My default controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\VirtualDomain;
use App\VirtualUser;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('home', [
'domains' => VirtualDomain::all(),
]);
}
public function domain($name)
{
$domain = VirtualDomain::where('name', $name)->first();
return view('domain', [
'domain' => $domain,
'emails' => VirtualUser::where('domain_id', $domain->id),
]);
}
}
and a couple of simple blades
home.blade.php
<p>
{{ $domains->count() }} domains
</p>
<table class="table-summary">
<thead>
<tr>
<th>name</th>
<th>emails</th>
<th>aliases</th>
<th class="table-summary-options">options</th>
</tr>
</thead>
<tbody>
#foreach ($domains as $domain)
<tr>
<td><a title="view emails for this domain" href="domain/{{ $domain->name }}">{{ $domain->name }}</a></td>
<td>{{ $domain->emails->count() }}</td>
<td>{{ $domain->aliases->count() }}</td>
<td class="table-summary-options"><a class="ui-action" title="remove this domain" href=""><img src="/img/ui/remove.png" alt="remove"></a></td>
</tr>
#endforeach
</tbody>
</table>
and domain.blade.php
<p>
<< - {{ $domain->name }} - {{ $emails->count() }} emails
</p>
<table class="table-summary">
<thead>
<tr>
<th>email</th>
<th class="table-summary-options">options</th>
</tr>
</thead>
<tbody>
#foreach ($emails as $email)
<tr>
<td><a title="view aliases for this domain" href="email/{{ $email->email }}">{{ $email->email }}</a></td>
<td class="table-summary-options"><a class="ui-action" title="remove this email" href=""><img src="/img/ui/remove.png" alt="remove"></a></td>
</tr>
#endforeach
</tbody>
</table>
The view outputs the correct number of emails under the domain with {{ $emails->count() }} - but the#foreach ($emails as $email)` does not loop.
When I modify the blade to simple use the emails from the domain variable ({{ $domain->emails->count() }} and #foreach ($domain->emails as $email)), I get the right count and the list is populated correctly.
What's making it go wrong when using the emails variable?
You have to make a small change for it to work
public function domain($name)
{
$domain = VirtualDomain::where('name', $name)->first();
return view('domain', [
'domain' => $domain,
'emails' => VirtualUser::where('domain_id', $domain->id)->get(),
]);
}
Without ->get() you will get a query builder instance while with get() will return a collection. In the foreach loop a collection can be iterated while a query builder instance can't be.
Hope this helps

Resources