ferraghue
3/7/2018 - 5:29 AM

Laravel Model Enumeration Trait

Laravel Model Enumeration Trait

Laravel Model Enumeration Trait

The Enum trait is a really useful way to allow you to pre-define all of the valid values for a given field on a model and enforce that their values are set appropriately. This basically allows you to treat a field as a menu without the database overhead of dealing with true enum fields or lookup tables.

Add the Enum trait to your model

namespace App;

use App\Traits\Enums;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Enums;
    ...
}

Sample Usage

Model App\Post has an enumerated field called status that we want to enforce specific values for.

class Post extends Model
{
    use Enums;

    // Define all of the valid options in an array as a protected property that
    //    starts with 'enum' followed by the plural studly cased field name
    protected $enumStatuses = [
        'Draft',
        'Scheduled',
        'Published',
        'Archived'
    ];

    // Alternately, if you use an associative array, the keys can be used
    //    to set the value as well. The full string will still be stored in the database.
    /* protected $enumStatuses = [
        'dr' => 'Draft',
        'sc' => 'Scheduled',
        'pu' => 'Published',
        'ar' => 'Archived'
    ]; */
    ...

Once you've defined this $enum property on the model, any time that field is set on any instance, a validation process will run to enforce that the value is being set properly:

$post = new App\Post;
$post->status = 'Something Invalid';
// Throws an InvalidEnumException
$post = App\Post::first();
$post->status = 'Draft';
// Sets the value to Draft as expected
// Key values will always work to set the value as well,
//   so using the non-associative array example, this will set status to 'Draft'
$post = App\Post::create([
  'status' => 0
]);

// Using the associative array example, this will set status to 'Published'
$post = App\Post::create([
  'status' => 'pu'
]);

Enumerations work really well in blade files too. Simply use the getEnum static helper:

@foreach( App\Post::getEnum('status') as $key => $value)
    {{ $key }}: {{ $value }}
@endforeach

Or use them with the LaravelCollective form builder:

{{ Form::select('status', App\Post::getEnum('status')) }}
<?php

namespace App\Exceptions;

use Exception;

class InvalidEnumException extends Exception
{
    //
}
<?php

namespace App\Traits;

use Illuminate\Support\Str;
use App\Exceptions\InvalidEnumException;

trait Enums
{
    /**
     * Enum property getter
     *
     * @param string $field
     * @return mixed|false
     */
    public static function getEnum(string $field)
    {
        $instance = new static;

        if ($instance->hasEnumProperty($field)) {
            $property = $instance->getEnumProperty($field);

            return $instance->$property;
        }

        return false;
    }

    /**
     * Check for the presence of a property that starts
     *     with enum for the provided attribute
     *
     * @param string $field
     * @param mixed $value
     * @return $this
     * @throws InvalidEnumException
     */
    public function setAttribute($field, $value)
    {
        if ($this->hasEnumProperty($field)) {
            if (!$this->isValidEnum($field, $value)) {
                throw new InvalidEnumException("Invalid value for " . static::class . "::$field ($value)");
            }

            if ($this->isKeyedEnum($field, $value)) {
                $value = $this->getKeyedEnum($field, $value);
            }
        }

        return parent::setAttribute($field, $value);
    }

    /**
     * Gets the expected enum property
     *
     * @param string $field
     * @return string
     */
    protected function getEnumProperty(string $field)
    {
        return 'enum' . Str::plural(Str::studly($field));
    }

    /**
     * Gets the enum value by key
     *
     * @param string $field
     * @param mixed $key
     * @return mixed
     */
    protected function getKeyedEnum(string $field, $key)
    {
        return static::getEnum($field)[$key];
    }

    /**
     * Is an enum property defined for the provided field
     *
     * @param string $field
     * @return boolean
     */
    protected function hasEnumProperty(string $field)
    {
        $property = $this->getEnumProperty($field);

        return isset($this->$property) && is_array($this->$property);
    }

    /**
     * Is the provided value a key in the enum
     *
     * @param string $field
     * @param mixed $key
     * @return bool
     */
    protected function isKeyedEnum(string $field, $key)
    {
        return in_array($key, array_keys(static::getEnum($field)), true);
    }

    /**
     * Is the value a valid enum in any way
     *
     * @param string $field
     * @param mixed $value
     * @return bool
     */
    protected function isValidEnum(string $field, $value)
    {
        return $this->isValueEnum($field, $value) ||
            $this->isKeyedEnum($field, $value);
    }

    /**
     * Is the provided value in the enum
     *
     * @param string $field
     * @param mixed $value
     * @return bool
     */
    protected function isValueEnum(string $field, $value)
    {
        return in_array($value, static::getEnum($field));
    }
}