DEV Community

Benjamin Delespierre
Benjamin Delespierre

Posted on

Identify models using their name with Laravel

Some database objects are atomic - i.e. they're just a name. Think of a Tag, think of a Role, think of a Country... There's really nothing more than the name - or at least the other properties are entirely optional. In such cases you might want an unique index on the name property, wouldn't you?

Now the name uniquely identifies your object, why not use the name to fetch it?

// instead of...
$role = Role::where('name', '=', 'admin')->firstOrCreate([]);

// ...you want to do 
$role = Role::wrap('admin');
Enter fullscreen mode Exit fullscreen mode

Well, that's precisely what today's snippet does for you!

Note: the following trait has been designed to create objects that are not found. If you want to protect yourself from typos being written in database I suggest you define the setNameAttribute method to implement the security you need.

Implementation

app/HasName.php

namespace App;

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;

trait HasName
{
    public static function findFromName(string $name): self
    {
        return self::whereName($name)->firstOrFail();
    }

    public static function findOrCreate(string $name): self
    {
        try {
            return self::findFromName($name);
        } catch (ModelNotFoundException $e) {
            return self::create(compact('name'));
        }
    }

    public static function wrap($name): self
    {
        if (is_string($name)) {
            $name = self::findOrCreate($name);
        }

        if (is_array($name)) {
            $name = self::firstOrCreate($name);
        }

        if (! $name instanceof self) {
            throw new \InvalidArgumentException("\$name should be string, array, or " . self::class);
        }

        return $name;
    }

    public static function fromArray(array $array): Collection
    {
        return (new Collection($array))->map(fn($item) => self::wrap($item));
    }
}
Enter fullscreen mode Exit fullscreen mode

app/Country.php

<?php

namespace App;

use App\HasName;
use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    use HasName;

    protected $fillable = ['name'];
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)