86

Laravel has a <select> form helper which takes as input a dictionary. I like to keep the values for all of these in a central place. For example, I might have an enum that looks like this:

$phoneTypes = [
    'CELL' => "Cellular",
    'HOME' => "Home",
    'WORK' => "Work",
];

Which I want to use in both my view/template, and in the database:

Schema::create('customers', function (Blueprint $table) {
    $table->increments('id');
    $table->enum('pri_phone_type',array_keys($phoneTypes));
    ...
});
  1. Is there a recommended place to put these?
  2. Can I make them global so I can access them easily in all my views?
mpen
  • 272,448
  • 266
  • 850
  • 1,236

7 Answers7

115

Update: PHP 8.1 has finally brought native support for enums!

See more here:

https://stitcher.io/blog/php-enums https://php.watch/versions/8.1/enums https://www.php.net/manual/en/language.enumerations.php

My original answer below no longer applies, but if you're working with an older version of PHP...


Original answer

You have several options for handling enums. Before we look at a few though, I would first strongly encourage you not to use the DB enum column type.

Database enums are problematic for a number of reasons. I suggest reading this article for example:

http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/

So with that let's look at a few other options.

Using Laravel config

Since you're using Laravel, one very simple option is to stick an array of options in a config file.

Say you create a new file config/enums.php with the following:

return [
    'phone_types' => [
        'CELL' => "Cellular",
        'HOME' => "Home",
        'WORK' => "Work",
    ]
];

You can now access config('enums.phone_types') anywhere in your code, including your Blade template.

Using a PHP package

@Banford's answer shows how to do basic enum-type behavior with class constants. If you like that approach, I recommend looking at this article and package which builds on this concept to provide strongly type enums:

https://stitcher.io/blog/php-enums

https://github.com/spatie/enum

You would create a class like this:

/**
 * @method static self cell()
 * @method static self home()
 * @method static self work()
 */
class PhoneTypes extends Enum
{
}

And now you can call PhoneTypes::home() in your app. Check out the documentation for that package to see how you can create a map of values, if you want.

Using DB relationships

If you really want to manage your options in the database, I'd create a separate phone_types database table and create a relationship with your customers table. This is still a much better option than using enum column type.

jszobody
  • 28,495
  • 6
  • 61
  • 72
  • 21
    Why would you prefer to create separate tables for each of a dozen very tiny lists that very rarely change? – mpen Sep 07 '14 at 00:25
  • 3
    Among other things, I can query for customers that have a cell phone on file? And the moment I'm convinced something won't change... it does. I'd prefer to design in advance so that it's trivial to make that change, personally. – jszobody Sep 07 '14 at 00:26
  • 3
    I should clarify: there are certainly times when I have a `varchar` column type with a set of possible values that my app controls. However if I want the database to regulate the possible values, I use a relationship table. I very much don't like using `enum` at the database level, here's one article why: http://komlenic.com/244/8-reasons-why-mysqls-enum-data-type-is-evil/ – jszobody Sep 07 '14 at 00:28
  • What if you need to perform logic based on a value? Like, say, if someone chose "CELL" then a new dropdown would appear with Android/iOS selections, thus you need to reference the value in code. Would you base this conditional logic on the numeric primary key found in the database? – mpen Sep 07 '14 at 00:35
  • I would either base it on the numeric key, or on the name in the relationship table. So you may have a `phone_type` table with `ID` and `Name` columns. In Laravel I can reference the relationship and do conditional logic like `if($customer->phoneType->name == 'Cellular') ...`. – jszobody Sep 07 '14 at 00:38
  • 1
    Basing it on the name is probably the worst idea. I *already* don't like "Cellular"; I might want to change it to "Mobile" or just "Cell" down the road -- that would break all my code. Anyway, I appreciate your suggestions. I think I'm going to go with `Config::get` right now, but I'll keep what you said in mind. – mpen Sep 07 '14 at 00:43
  • @mpen Doctrine (as for checking column existence and advanced migration tools) doesn't support ENUM type for schema creation. That ALONE is a good enough reason to avoid enums if you wish to use the schema blueprint class in laravel. – ProfileTwist Jun 02 '16 at 03:21
  • @ProfileTwist Huh? I'm not using Doctrine, I'm using Laravel. [Ctrl+F `->enum`](https://laravel.com/docs/5.2/migrations). And it's not like I couldn't `raw` around it. I don't believe in doing inferior work because my framework doesn't allow it. – mpen Jun 02 '16 at 17:15
  • 1
    Laravel is powered by symfony and symfony is powered by doctrine for database management. – ProfileTwist Jun 04 '16 at 22:45
  • 1
    @mpen scroll down in the page you linked to find `Note: Modifying any column in a table that also has a column of type enum is not currently supported.` this exception is thrown by Doctrine dbal. – ProfileTwist Jun 04 '16 at 22:52
  • @ProfileTwist Laravel uses the Doctrine DBAL classes, yes, but not the ORM. Laravel's own Eloquent ORM actually does support the `enum` column type (just not renaming it, as you pointed out). Regardless, I'm obviously in complete agreement that it's good to avoid the `enum` column type anyway. =) – jszobody Jun 04 '16 at 22:59
  • Well...I just encountered the problem of adding a value to an existing enum, and just like you warned, Laravel handles it miserably. Just errors out. Had to write some raw SQL to update the columns. – mpen Oct 14 '16 at 15:59
  • The article you link to is not about not using enums (despite the title), it's about not *mis*using them. The author clarifies this in the comments, too. – Chuck Le Butt Jun 05 '18 at 16:13
  • 1
    While I agreed that having a separate table for the values, I think it's not correct: column types should reflect column possible values, so as varchar and integer hold sequences of characters and integer numbers, an enum colum can hold a pre-defined set of values. – fudo Feb 20 '19 at 14:37
  • @fudo column types should reflect the _type_ of possible values, not the actual range of values. enum column types will get you into trouble. – jszobody Feb 22 '19 at 14:41
77

I disagree with the accepted answer here. I feel that enums can be very useful for this kind of thing. I prefer to treat enums as types, and implement the methods you need on the Enum base class to give you the functionality you need such as getting a dictionary.

My simple example below:

abstract class PhoneType extends Enum {
    const Cell = "Cellular";
    const Home = "Home";
    const Work = "Work";
}

abstract class Enum {
    static function getKeys(){
        $class = new ReflectionClass(get_called_class());
        return array_keys($class->getConstants());
    }
}

Example usage:

PhoneType::getKeys();

See PHP and Enumerations for further details and a more in depth example.

Community
  • 1
  • 1
Banford
  • 2,539
  • 4
  • 23
  • 35
  • 4
    A good use-case for enum is gender. You'll usually just be dealing with male, female or unspecified. There is no point in an extra table as these three values cover all possible cases. Of course, there could be a use-case for more gender designations for certain projects but that is the exception. – Ixalmida May 02 '16 at 19:20
  • 3
    It may still be better to change your enums to tinyint and then simply map each constant to an integer! That way you can always add a fourth phone (which is likely to happen). An ALTER table command is expensive and not worth risking. Furthemore, some frameworks like doctrine do not even support Enum for schema creation. – ProfileTwist Jun 02 '16 at 03:20
  • Hi Banford. I have tried all the enum solutions that were explained here. Only your solution worked great and easy code automation in PHPStorm. Thank you for sharing a good solution. – akio an Oct 05 '21 at 07:02
15

Building on @Banfords answer, with PHP7 constants can now be arrays:

class User extends Authenticatable
{
    /**
     * The possible genders a user can be.
     */
    const GENDER = [
        'Male',
        'Female',
        'Unspecified'
    ];

...
Kirill Fuchs
  • 13,446
  • 4
  • 42
  • 72
13

Just had similar issue, for me Eloquent Accessors and mutators worked the best. For this question it would go like:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
    /**
    * @var array
    */
    protected $phoneTypes = [
        'Cellular',
        'Home',
        'Work'
    ];

   /**
    * @param int $value
    * @return string|null
    */
    public function getPhoneTypeAttribute($value)
    {
        return Arr::get($this->phoneTypes, $value);
    }
}

Please note that in database you should save numeric values, where 0 is cell, 1 is home and 2 is work. Secondly it would be wise to use translations here instead protected property.

lchachurski
  • 1,770
  • 16
  • 21
13

In addition to @Banford's answer:

I have recently put together a package which makes working with enums in Laravel much nicer. It's a combination of various implementations I had found while researching how to do the very same thing (hence why I'm here).

https://github.com/BenSampo/laravel-enum

In this case, you could do something like the following:

final class PhoneTypes extends Enum
{
    const Cellular = 0;
    const Work = 1;
    const Home = 2;
}

The values can then be accessed using:

PhoneTypes::Work // 1

I would recommend always setting the values to integers and subsequently storing them in the DB as ints.

The base Enum class has methods for getting all keys and values as arrays. The package also features a couple of other benefits which may be useful in this case such as validation - so that a user couldn't add a non-existent value to the DB.

There's also a generator which is pretty handy.

I hope this comes in useful for someone.

BenSampo
  • 559
  • 5
  • 8
  • Neat, but how is this specific to Laravel? Looks like many of the other php-enum implementations. – mpen Sep 11 '17 at 23:21
  • @mpen The enum implementation is pretty generic, you're right. The Laravel specifics are in the generator and the validation. – BenSampo Sep 12 '17 at 10:09
9

You should not use enum at all.

The official Laravel 5.1 documentation states:

Note: Renaming columns in a table with a enum column is not currently supported.

It happens when you have a enum column in your database table. Whether you are trying to rename another column, or change another column to nullable, the bug will appear. It's an issue with Doctrine\DBAL.

Unknown database type enum requested

Even with laravel 5.8, problem is not resolved.

I need to add that you will have the same problem when adding available options into enum column declaration.

It brings me to a conclusion that You should use enum with care. or even You should not use enum at all.

Here is an example of how difficult would it be adding available options into enum column declaration

say you have this:

Schema::create('blogs', function (Blueprint $table) {
    $table->enum('type', [BlogType::KEY_PAYMENTS]);
    $table->index(['type', 'created_at']);
...

and you need to make more types available

public function up(): void
{
    Schema::table('blogs', function (Blueprint $table) {
        $table->dropIndex(['type', 'created_at']);
        $table->enum('type_tmp', [
            BlogType::KEY_PAYMENTS,
            BlogType::KEY_CATS,
            BlogType::KEY_DOGS,
        ])->after('type');
    });

    DB::statement('update `blogs` as te set te.`type_tmp` = te.`type` ');

    Schema::table('blogs', function (Blueprint $table) {
        $table->dropColumn('type');
    });

    Schema::table('blogs', function (Blueprint $table) {
        $table->enum('type', [
            BlogType::KEY_PAYMENTS,
            BlogType::KEY_CATS,
            BlogType::KEY_DOGS,
        ])->after('type_tmp');
    });

    DB::statement('update `blogs` as te set te.`type` = te.`type_tmp` ');

    Schema::table('blogs', function (Blueprint $table) {
        $table->dropColumn('type_tmp');
        $table->index(['type', 'created_at']);
    });
}
Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
0

Let's say you need a dropdown of choices

Try this:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
    const PHONE_TYPES = [
        [
            'label' => 'Cellular',
            'value' => 'Cellular',
        ],
        [
            'label' => 'Home',
            'value' => 'Home',
        ],
        [
            'label' => 'Work',
            'value' => 'Work',
        ],
    ];


    public function getPhoneTypesLabelAttribute()
    {
        return collect(static::PHONE_TYPES)->firstWhere('value', $this->phone_types)['label'] ?? '';
    }
}

While in your controller, let's say under the create method do this:

public function create(Customer $customer)
{
    return response([
        'meta' => [
            'customer'   => Customer::get(['id', 'first_name']),
            'phone_types' => Customer::PHONE_TYPES,
        ],
    ]);
}

Then, in your blade template

dd($phone_types);
matiaslauriti
  • 7,065
  • 4
  • 31
  • 43
Akere Achu
  • 19
  • 1
  • 1
    Hmm.. a model doesn't seem like the right place for the enum data, especially if phone types aren't exclusive to customers. – mpen Apr 14 '21 at 17:55
  • @mpen. Yes, you are right. but I think in cases where the Dropdown is only for that model it could be used in my opinion. I find it easy to attach each dropdown to the model's view blade template like $customer['phone_types'] = Customer::PHONE_TYPES :).. and you can always go back and increase the data on phone_types with no worries. – Akere Achu Apr 15 '21 at 08:08