How to use relationship value in Laravel Form Facade?

Viewed 3727

I want to use the values of attributes on a model's relationship with Form::label and Form::text. The Form helper was removed from Laravel, so I use 'Form' => 'Collective\Html\FormFacade' instead.

Here is the relationship in the Order Model:

<?php namespace App\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class Order extends \Eloquent
{
    use SoftDeletes;

    public function account_number()
    {
        return $this->belongsTo('\App\Models\Account_number', 'product_id', 'id');
    }
}

And here's the Blade template with Form. The text in the account_number <td> will show:

{"id":4,"user_id":52,"account_type":"alipay","account_no":"xxxxxx","account_name":"xxxxxx","phone":"xxxxxx","created_at":"2017-11-15 14:43:51","updated_at":"2017-11-15 14:43:51","deleted_at":null}
{!! Form::model($order, array('files' => true)) !!}
<table border="1">
<tr>
  <td>{!! Form::label('out_trade_no', 'out_trade_no: ') !!}</td>
  <td>{!! Form::text('out_trade_no')!!}</td>
</tr>
<tr>
  <td>{!! Form::label('account_number', 'account_number: ') !!}</td>
  <td>{!! Form::text('account_number')!!}</td>
</tr>
</table>

But I want to show inputs for each account_number attribute separately, not as a JSON string.

I have tried with:

<tr>
  <td>{!! Form::label('account_number.id', 'account_number: ') !!}</td>
  <td>{!! Form::text('account_number.id')!!}</td>
</tr>

or

<tr>
  <td>{!! Form::label('account_number->id', 'account_number: ') !!}</td>
  <td>{!! Form::text('account_number->id')!!}</td>
</tr>

or

<tr>
  <td>{!! Form::label('account_number', 'account_number: ') !!}</td>
  <td>{!! Form::text('account_number["id"]')!!}</td>
</tr>

...but none of these work. They all leave this <td> empty.

4 Answers

To create input controls for a model's relationships using the FormBuilder tools provided by Laravel Collective, use the following syntax:

{!! Form::model($order, ...) !!} 
    ...
    {!! Form::label('account_number[id]', 'account number: ') !!}
    {!! Form::text('account_number[id]') !!}
    ...
{!! Form::close() !!}

Note the lack of quotation marks around id. The example in the question contains quotes around the related model's id attribute, which breaks this magic. The code above renders the following input element using the value of the id attribute on the Order model's account_number relationship:

<input name="account_number[id]" type="text" value="4">

The format shown in the input element's name attribute enables PHP to parse the POST data as an array. We can fetch the value submitted to a controller method like in this example:

public function save(Request $request) 
{
    $accountNumber = $request->get('account_number'); 

    echo $accountNumber['id']; // '4'
    ...
}

This feature is important—FormBuilder generates input elements designed to work with PHP's automatic grouping of request data into arrays. If we submit multiple input elements on a form for a model's relationship, Laravel makes it easy to save the result:

public function update(Request $request, $orderId) 
{
    Order::with('account_number')->find($orderId)
        ->fill($request->all())
        ->account_number->fill($request->account_number)
        ->push();
}

Let's do something more in-depth understanding.

While generating a text input element, FormBuilder will look for the value in the session for the value in the old input data then it'll look in the model instance if one is set. Otherwise it'll just use empty.
Before it gets the appropriate value, it will transform the key which we specified, such as account_number.id, account_number->id, account_number["id"]. Focus on the transformKey function

protected function transformKey($key)
{
    return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
}

Let's call it with the keys you specified one by one:

  1. account_number.id => account_number_id
  2. account_number->id => account_number->id
  3. account_number["id"] => account_number."id"

Then it will split the key by . return a array $keys. And check if exists nested model with $keys[0], otherwise return the value of master model with transformed key. getFormValue()

No matter it exists nested model or not, it will get the value through data_get() function.

Obviously, the first key and the second you specified doesn't work. The third one account_number."id", it will find the nested model successfully but it won't retrieve the attribute through the way $nestedModel->{"id"}. Use account_number[id] instead, it works well.

<td>{!! Form::text('account_number[id]')!!}</td>

The way you're using LaravelCollective's FirmBuilder with model binding is wrong. Let's suppose you have data in orders like:

{
id: 1,
....., // some other fields
account_number: {
 "id":4,
 "user_id":52,
 "account_type":"alipay",
 "account_no":"xxxxxx",
 "account_name":"xxxxxx",
 "phone":"xxxxxx",
 "created_at":"2017-11-15 14:43:51",
 "updated_at":"2017-11-15 14:43:51",
 "deleted_at":null
}
}

So now if you want to display account_number id in textfield then you can write your form builder in this way:

{!! Form::model($order, ...) !!} 
    ...
    <tr>
    <td>{!! Form::label('account_number', 'account number: ') !!}</td>
    <td>{!! Form::text('account_number', $order->account_number->id) !!}</td>
    </tr>
    ...
{!! Form::close() !!}

or

<td>{!! Form::text('account_number[id]') !!}</td>

But in the second answer you'll get issue when submitting form. Because besides getting the value you wanted, that line of code also renames your input name attributes' value to account_number[id] instead of account_number. So the best option is to use the first solution. Here is the link for form model binding in LaravelCollective : https://laravelcollective.com/docs/master/html#form-model-binding

After trying to implement on my own environment, my conclusion-

{!! Form::model($order, array('files' => true)) !!}
 <table border="1">
   <tr>
     <td>{!! Form::label('out_trade_no', 'out_trade_no: ') !!}</td>
     <td>{!! Form::text('out_trade_no')!!}</td>
   </tr>
   <tr>
     <td>{!! Form::label('account_number', 'account_number: ') !!}</td>
     <td>{!! Form::text('account_number[id]')!!}</td>
   </tr>
 </table>
Related