Lloyd Owen
Lloyd Owen

Reputation: 381

Laravel Form best way to store polymorphic relationship

I have a notes model. Which has a polymorphic 'noteable' method that ideally anything can use. Probably up to 5 different models such as Customers, Staff, Users etc can use.

I'm looking for the best possible solution for creating the note against these, as dynamically as possible.

At the moment, i'm adding on a query string in the routes. I.e. when viewing a customer there's an "Add Note" button like so:

route('note.create', ['customer_id' => $customer->id])

In my form then i'm checking for any query string's and adding them to the post request (in VueJS) which works.

Then in my controller i'm checking for each possible query string i.e.:

if($request->has('individual_id'))
{
   $individual = Individual::findOrFail($request->individual_id_id);
   // store against individual
   // return note
   }elseif($request->has('customer_id'))
   {
      $customer = Customer::findOrFail($request->customer_id);
      // store against the customer
      // return note
   }

I'm pretty sure this is not the best way to do this. But, i cannot think of another way at the moment.

I'm sure someone else has come across this in the past too!

Thank you

Upvotes: 1

Views: 1174

Answers (4)

cednore
cednore

Reputation: 885

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Requests\NoteStoreRequest  $request
 * @return \Illuminate\Http\Response
 */
public function store(NoteStoreRequest $request) {
    // REF: NoteStoreRequest does the validation

    // TODO: Customize this suffix on your own
    $suffix = '_id';

    /**
     * Resolve model class name.
     * 
     * @param  string  $name
     * @return string
     */
    function modelNameResolver(string $name) {
        // TODO: Customize this function on your own
        return 'App\\Models\\'.Str::ucfirst($name);
    }

    foreach ($request->all() as $key => $value) {
        if (Str::endsWith($key, $suffix)) {
            $class = modelNameResolver(Str::beforeLast($key, $suffix));
            $noteable = $class::findOrFail($value);
            return $noteable->notes()->create($request->validated());
        }
    }

    // TODO: Customize this exception response
    throw new InternalServerException;
}

Upvotes: 0

Saddam
Saddam

Reputation: 1206

In order to optimize your code, dont add too many if else in your code, say for example if you have tons of polymorphic relationship then will you add tons of if else ? will you ?,it will rapidly increase your code base.
Try instead the follwing tip.

when making a call to backend do a maping e.g

$identifier_map = [1,2,3,4];

// 1  for Customer
// 2  for Staff
// 3  for Users 
// 4  for Individual

and so on

then make call to note controller with noteable_id and noteable_identifier

route('note.create', ['noteable_id' => $id, 'noteable_identifier' => $identifier_map[0]])

then on backend in your controller you can do something like

if($request->has('noteable_id') && $request->has('noteable_identifier'))
{
    $noteables = [ 'Customers', 'Staff', 'Users','Individual']; // mapper for models,add more models.
       
        $noteable_model = app('App\\'.$noteables[$request->noteable_identifier]);
        $noteable_model::findOrFail($request->noteable_id);

}

so with these lines of code your can handle tons of polymorphic relationship.

Upvotes: 1

Cesar.T
Cesar.T

Reputation: 76

Not sure about the best way but I have a similar scenario to yours and this is the code that I use.

my form actions looks like this

action="{{ route('notes.store', ['model' => 'Customer', 'id' => $customer->id]) }}"
action="{{ route('notes.store', ['model' => 'User', 'id' => $user->id]) }}"

etc..

And my controller looks this

public function store(Request $request)
{
    // Build up the model string
    $model = '\App\Models\\'.$request->model;
    // Get the requester id
    $id = $request->id;

    if ($id) {
        // get the parent 
        $parent = $model::find($id);
        // validate the data and create the note
        $parent->notes()->create($this->validatedData());
        // redirect back to the requester
        return Redirect::back()->withErrors(['msg', 'message']);
    } else {
        // validate the data and create the note without parent association
        Note::create($this->validatedData());
        // Redirect to index view
        return redirect()->route('notes.index');
    }
}

protected function validatedData()
{
    // validate form fields
    return request()->validate([
        'name' => 'required|string',
        'body' => 'required|min:3',
    ]);
}

Upvotes: 0

Bomzan
Bomzan

Reputation: 36

The scenario as I understand is:
-You submit noteable_id from the create-form
-You want to remove if statements on the store function.

You could do that by sending another key in the request FROM the create_form "noteable_type". So, your store route will be

route('note.store',['noteableClass'=>'App\User','id'=>$user->id])

And on the Notes Controller:

public function store(Request $request)
{
    return Note::storeData($request->noteable_type,$request->id);
}

Your Note model will look like this:

class Note extends Model
{
    public function noteable()
    {
        return $this->morphTo();
    }
    public static function storeData($noteableClass,$id){
        $noteableObject = $noteableClass::find($id);
        $noteableObject->notes()->create([
            'note' => 'test note'
        ]);
        return $noteableObject->notes;
    }
}

This works for get method on store. For post, form submission will work.

Upvotes: 0

Related Questions