Laravel form binding with one to one relationships - php

I have an Account model that has a polymorphic relation to an Address model. This is set up as a one-to-one releationship set up like so:
Account:
public function address()
{
return $this->morphOne('Address', 'hasAddress', 'add_hasaddress_type', 'add_hasaddress_id', 'act_id');
}
Address:
public function hasAddress()
{
return $this->morphTo('hasAddress', 'add_hasaddress_type', 'add_hasaddress_id');
}
On my form to edit the account, I also have the address fields. I can bind my account object simply enough by doing:
{{ Form::model($account, array('route' => array('accounts/edit', $account->act_id), 'method' => 'put')) }}
{{ Form::label('act_name', 'Account Name:') }}
{{ Form::text('act_name', Input::old('act_name')) }}
and that fills in the fields properly. But, how do I populate the address fields? From what I researched, I need to do:
{{ Form::text('address.add_city', Input::old('address.add_city')) }}
To access the relation's values, but this doesn't work.
I also tried
{{ Form::text('address[add_city]', Input::old('address[add_city]')) }}
as suggested by a SO with a similar title. Both of these I tried with and without the old input. Does this just not work with poymorphic relations or am I doing something wrong?
Also, how do you handle these forms in the controller?
Nothing about relations is in the form model binding documentation, and doing a search mainly brings up people asking for one-to-many binding.

It works with any *-to-one relation (for many-to-many, ie. a collection of models it won't work):
// prepare model with related data - eager loading
$account = Account::with('address')->find($someId);
// or lazy loading
$account = Account::find($someId);
$account->load('address');
// view template
{{ Form::model($account, ...) }}
Account: {{ Form::text('acc_name') }}
City: {{ Form::text('address[add_city]') }}
{{ Form::close() }}
No need for Input::old or whatsoever, null is enough as default value. Laravel will fill the data in this order (Docs are wrong here!):
1. old input
2. bound data
3. value passed to the helper
Mind that you must load the relation (dynamic call won't work in this case).
Another thing is processing the input later - Laravel will not automatically hydrate the related model, so you need something like:
$accountData = Input::only(['acc_name', ... other account fields]);
// or
$accountData = Input::except(['address']);
// validate etc, then:
$account->fill($accountData);
$addressData = Input::get('address');
// validate ofc, then:
$account->address->fill($addressData);

Related

Delete data from the database with Laravel 5.4

I am sort of new to the Laravel framework and I am building just a simple blog. I can create a blog, show a blog and show a overview of all blogs. Now I would like to delete a blog. So, I have created a delete button in my view with a route link which will pass also the id of the article. Then, in my routes file I specify a delete request and a controller method. In the method I find the id and try to delete the row with the id I specified in the route/view.
This doesn't work. Instead of activate the destroy/delete method it shows the article instead of deleting it and activates the show method instead of the delete method. Can somebody help me out, What do I wrong?
View.blade.php
<a href="{{route('nieuws.destroy', ['id' => $blog->id])}}" onclick="return confirm('Weet je dit zeker?')">
<i class="fa fa-trash"></i>
</a>
Route
Route::group(['middleware' => 'auth'], function () {
Route::get('/aanvragen', 'aanvragenController#index')->name('aanvragen.index');
Route::get('/logout' , 'Auth\LoginController#logout')->name('logout');
Route::get('/nieuws/toevoegen', 'blogController#create')->name('blogs.add');
Route::post('/nieuws/store', 'blogController#store')->name('nieuws.store');
Route::delete('/nieuws/{id}', 'blogController#destroy')->name('nieuws.destroy');
});
Route::get('/nieuws', 'blogController#index')->name('blogs.index');
Route::get('/nieuws/{blog}', 'blogController#show')->name('blogs.show');
Controller methods
Delete/Destroy
public function destroy($id) {
$blog = Blog::find($id);
$blog->delete();
return redirect('/nieuws');
}
Show
public function show(Blog $blog) {
dd('show');
return view('blogs.show', compact('blog'));
}
A delete() route requires you to POST your data.
HTML forms only supports GET and POST, other methods like DELETE, PUT, etc are not supported, that's why Laravel uses the _method to spoof methods which are not supported by HTML forms.
You do not want use GET in these cases, since someone can send a user the url (http://yoursite.com/blog/delete/1) in an IM or via email. The user clicks and the blog is gone.
Define your route as it would be when using resource controllers, so:
Route::delete('/nieuws/{id}', 'blogController#destroy')->name('nieuws.destroy');
And either use a form with the delete method:
// apply some inline form styles
<form method="POST" action="{{ route('nieuws.destroy', [$blog->id]) }}">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit">Delete</button>
</form>
Or do some javascript magic as the link SR_ posted in his comment on your OP.
One more thing, add some sort of validation in your destroy action. Right now when you provide a non-existing id or something else, you will get a 500 error, instead you want to have a 404.
public function destroy($id)
{
$blog = Blog::findOrFail($id);
$blog->delete();
return redirect('/nieuws');
}
I think you need to update your destroy function like:
public function destroy($id) {
$blog = DB::table('blog')->where('id',$id)->delete();
return redirect('/nieuws');
}
And update your view code like:
<a href="{{route('nieuws.destroy', [$blog->id])}}" onclick="return confirm('Weet je dit zeker?')">
<i class="fa fa-trash"></i>
</a>
Hope this work for you!
I'm also new to Laravel but I made it work through this way:
(I use 'Article' as the model's name and the resource "method" in the route stands for a bunch of useful routes including the route you wrote)
Controller:
public function destroy($id){
Article::find($id)->delete();
//$article = Article::find($id);
return redirect()->back()->withErrors('Successfully deleted!');
}
Route:
Route::resource('article','ArticleController');
However, I think the problem lies in the default definition of database's name of your model. Laravel will assume that you have a database named blogs since you have a model named "blog". Are you having the database's name right?
To use DELETE HTTP Verb, your form should consists of the POST method and settings the method_field('DELETE')
Example:
<form method="POST" action="{{ route('xxx.destroy', $xxx->id) }}">
{{ csrf_field }}
{{ method_field('DELETE') }}
</form>

Access attribute of a 'hasOne' related object

In my User class I have this function:
public function profile() {
return $this->hasOne('App\Profile');
}
In the controller I used $users = User::all() to get all the users and then pass it to the view using with('users', $users)
In the view where I want to display all of my users profiles I used foreach loop to get to each user data like:
#foreach($users as $user)
<div> {{ $user->profile->some_prfiles_table_column_name }} </div>
But i got an error, So I had to access it using square brackets like this:
{{ $user->profile['some_profiles_table_column_name'] }}
And in another view, where i retrieved only one user by id User::find($id) then i can access the user profile attributes normally as an object NOT array, Like:
{{ $user->profile->some_profiles_table_column_name }}
What i want to understand is Why i'm getting an array instead of an object? Is there is something wrong or this is normal in laravel?
Thanks in advance
You're not getting an array. Eloquent Models implement PHP's ArrayAccess interface, which allows you to access the data as if it were an array.
The problem you're having is that one of your users does not have an associated profile. When that happens, $user->profile will be null. If you attempt to access an object property on null, you'll get a "Trying to get property of non-object" error. However, if you attempt to access an array property of null, it'll just return null without throwing an error, which is why your loop appears to work as an array.
Illustrated with code:
foreach ($users as $user) {
// This will throw an error when a user does not have a profile.
var_export($user->profile->some_profiles_table_column_name);
// This will just output NULL when a user does not have a profile.
var_export($user->profile['some_profiles_table_column_name'];
}
So, presumably, you'll want to handle the situation in your code when the user does not have a profile:
#foreach($users as $user)
<div> {{ $user->profile ? $user->profile->some_profiles_table_column_name : 'No Profile' }} </div>
#endforeach

Creating a front end relationship in octobercms

I have created the front end relationship between my models image and album by using hasMany and belongsTo however I am not getting any results displayed and
{{ dump(images) }} and {{ dump(albums) }} gives Object variables NULL 0 NULL
Any Ideas why the relationship is not being picked up?
Album
public $hasMany = [
'images' => [ 'Acme\Library\Models\Image' ,'key'=>'album_id'],
];
Image
public $belongsTo = [
'albums' => ['Acme\Library\Models\Album']
];
album-details.htm
{{ dump(images) }}
{% for image in images %}
{{image.id|raw }}
{% endfor %}
if I run a dump on {{ dump(record) }} I get
Object variables
Acme\Library\Models\Album
bootValidation() method Boot the validation trait for this model.
forceSave() method Force save the model even if validation fails.
validate() method Validate the model instance
isAttributeRequired() method Determines if an attribute is required based on the validation rules.
errors() method Get validation error message collection for the Model
validating() method Create a new native event for handling beforeValidate().
validated() method Create a new native event for handling afterValidate().
rules array(0)
timestamps boolean
table string
relationConfig string
hasMany array(1)
I can see using the builder plugin that record is defined {% set records = builderList.records %}
Does albums/images need to be defined in this manner also?
I you send record variable to the view and record is an instance of Album you can access your images this way.
{% for image in record.images %}
{{image.id|raw }}
{% endfor %}

Laravel - Updating OneToOne relationship

I have two models 'User' and 'Profile'.
'email' field is in User model whereas 'name' field is in Profile model.
'profiles' table has a foreign key of 'user_id'.
I searched a lot but couldn't find a proper solution on how I can update both of these entities in one go.
In my ProfileController, I am doing this but I am sure there is a better way. Please help.
public function update($id)
{
$profile = Profile::where('id', $id);
$profile->name = 'Jon';
$profile->save();
$user = User::where('id', $profile->user_id);
$user->email = 'newjon#example.com';
$user->save();
}
My Profile model has
public function user()
{
return $this->belongsTo('User');
}
And my User model has
public function profile()
{
return $this->hasOne('Profile');
}
You can't do it in one go.
However you could simplify it a bit, by leveraging Laravel features, like this (and do it in one-go-like way):
1 Controller edit
$profile = Profile::with('user')->find($id);
// make sure you eager load the user for below to work
2 View
{{ Form::model($profile) }}
{{ Form::text('name') }}
{{ Form::text('user[email]') }}
{{ Form::close() }}
this will autopopulate your profile data (and user data too)
3 Controller update
$profile = Profile::find($id);
$profile->fill(Input::only(.. fields you want to update ..));
$profile->user->fill(Input::get('user')); // array of user data form the form
$profile->push(); // save both models in one go BUT separate queries
Also make sure you have fillable on your models, so fill will does its job.
Another way would be using model events, but I wouldn't do it that way.

Many-to-Many Eloquent relationship update with Laravel Form Model Binding & Checkboxes

I have 3 tables:
doors
id
name
image
colors
id
name
image
door_colors
id
door_id
color_id
and 2 models with a many-to-many relationship (each door comes in a variety colors, and many colors overlap door-to-door):
Door Model
class Door extends Eloquent {
public function colors()
{
return $this->belongsToMany('Color', 'door_colors');
}
}
Color Model
class Color extends Eloquent {
public function doors()
{
return $this->belongsToMany('Door', 'door_colors');
}
}
I want to create a form where I can edit the door, and update the available colors via checkboxes.
This is my Admin Doors Controller
class AdminDoorsController extends AdminController {
public function edit($id)
{
$data['door'] = Door::find($id);
$data['colors'] = Color::all();
return View::make('admin/doors/form', $data);
}
}
and the Admin Doors Form View
{{ Form::model($door) }}
Colors:
#foreach ($colors as $color)
{{ Form::checkbox('colors[]', $color->id) }} {{ $color->name }}
#endforeach
{{ Form::close() }}
Question 1: How do I make it so that as the checkboxes are outputted, the ones with an existing relationship with the current door are checked and the ones without are unchecked.
Question 2: Once I check the boxes and hit submit, how would I update the relationships? $door->colors()->detach(); to clear all existing ones for this door, then $door->colors()->attach($color_id_array); to create new ones based on an array of color ids?
Any input is appreciated!
Question 1: You should pass this into the view that contains your form, though it can also go right in the view, though that's not really best practice. Do something similar to this...
$checkeds = Door::find(1)->colors()->lists('id');
...where the door you are finding is the door that's being updated. Then before you output the checkbox in the loop, add
$checked = in_array($color->id, $checkeds) ? true : false;
Then you would change
{{ Form::checkbox('colors[]', $color->id) }}
{{ $color->name }}`
to
{{ Form::checkbox('colors[]', $color->id, $checked) }}
{{ $color->name }}
Question 2: There is actually a perfect method given to you for this. Use
$door->colors()->sync(Input::get('colors'));
It will both delete the old ones and add all the new ones in one shot.
Suppose you are modeling user and role and want to edit user with roles.
In your controller edit,
$user = User::find($id);
$roles = Role::lists('name', 'id'); // to populate all roles
In your template if you use select,
{{ Form::select('roles[]', $roles, array_pluck($user->roles, 'id'), ['multiple']) }}
In your controller update,
$inputs = Input::all();
$roles = $inputs['roles'];
$user->roles()->sync($roles);
// $user->fill($inputs);
// $user->save();

Categories