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');
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));
}
}
app/Country.php
<?php
namespace App;
use App\HasName;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
use HasName;
protected $fillable = ['name'];
}
Top comments (0)