Encapsulate URL attribute in Eloquent to manage routes in view uniformly

Keywords: PHP Laravel Attribute

It's not uncommon to have dozens or even hundreds of views in a Laravel application. Soon the route management used in the view will be out of control. Imagine how many of these things you did in the view.

<a href="{{ route('users.show', ['id' => $user->id]) }}">{{ $user->name }}</a>

If you want to modify the routing name or the default query parameter value, you need to carry out a large number of string replacement operations in the whole application, but this will bring repetitive workload, and may also miss some places.

What can we do to better deal with this problem? There are two ways to do this.

First: modify Eloquent

// app/Models/User.php

<?php

namespace App\Models;

class User {

  protected $appends = [
    'url'
  ];

  public function getUrlAttribute()
  {
    return route('users.show', $this);
  }
}

You can then use this in a view

<a href="{{ $user->url }}">{{ $user->name }}</a>

It feels clean, doesn't it? But for senior developers, you may want to take the next approach.

Second, use URL Presenter in eloquest

At first glance, you may be familiar with it. Yes, here we define a url attribute, but unlike the general writing method, we will return a presenter instance.

// app/Models/User.php

<?php

namespace App\Models;

use App\Presenters\User\UrlPresenter;

class User {

  protected $appends = [
    'url'
  ];

  public function getUrlAttribute()
  {
    return new UrlPresenter($this);
  }
}
// app/Presenters/User/UrlPresenter.php

<?php

namespace App\Presenters\User;

use App\User;

class UrlPresenter {

    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function __get($key)
    {
        if(method_exists($this, $key))
        {
            return $this->$key();
        }

        return $this->$key;
    }

    public function delete()
    {
        return route('users.delete', $this->user);
    }

    public function edit()
    {
        return route('users.edit', $this->user);
    }

    public function show()
    {
        return route('users.show', $this->user);
    }

    public function update()
    {
        return route('users.update', $this->user);
    }
}

And then you can use it like this

<a href="{{ $user->url->show }}">{{ $user->name }}</a>

As mentioned above, the current view doesn't care how we determine the URL, but just returns a URL. The advantage of this is that you can modify any route in the view by editing only two files instead of hundreds.

Posted by gewthen on Thu, 02 Apr 2020 18:35:43 -0700