Tag Archives forwebprogramming

Change field to enum in Laravel 10

In this post I will show you how to change an existing field in your Laravel application to change to enum.

I have a field status which was kept in line by having class constants defining the value for this field. But I really missed the database keeping the value in line though.

Changing the class constants to a PHP 8 enum and changing the database field to enum had some caveats so if you want to change your datatype to enum as well, follow along.

Change the class constants to an enum

I use the trait EnumEnhancements for the enum as this provides very handy helper methods:

ImportStatus::valueArray(): an array with the values of the enum
ImportStatus::valueList(): a string with the values of the enum: ‘pending, validating, importing, …’

<?php

declare(strict_types=1);

namespace App\Models\Enums;

use Othyn\PhpEnumEnhancements\Traits\EnumEnhancements;

enum ImportStatus: string
{
    use EnumEnhancements;

    case PENDING = 'pending';
    case VALIDATING = 'validating';
    case VALIDATION_FAILED = 'validation_failed';
    case VALIDATED = 'validated';
    case CREATING_IMPORT_JOBS = 'creating_import_jobs';
    case IMPORT_JOBS_CREATED = 'import_jobs_created';
    case IMPORTING = 'importing';
    case IMPORTED = 'imported';
    case FAILED = 'failed';
    case CANCELLED = 'cancelled';
    case FINISHED = 'finished';
}

Create a Laravel migration to change the field to an enum

NOTE: changing an existing field to an enum in your database requires you to do this with a DB statement. You cannot use a query builder for this or you’ll get the following error :

Unknown column type "enum" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information.

I change the existing column from a string to an enum in 4 steps:

  1. Get the current status of each record so it won’t get lost in the following steps
  2. Set all current values to NULL so that changing the datatype of the field won’t cause trouble with existing values
  3. Do the actual changing of the datatype of the field to enum
  4. Change all statuses back to their original value but act on cases where the existing value is not valid with the current enum values

<?php

declare(strict_types=1);

use App\Models\Enums\ImportStatus;
use App\Models\ImportFile;
use Illuminate\Database\Migrations\Migration;

return new class () extends Migration {
    public function up(): void
    {
        // 1. Get the current status for each row
        $importFiles = DB::table('posts')->get(['id', 'status']);

        // 2. Set all statuses to NULL
        DB::table('posts')->update(['status' => null]);

        // 3. Change the column to an enum
        $this->changeStatusToEnum();

        // 4. Set the status for each row to the value we got in step 1
        foreach ($products as $product) {
            $validStatuses = collect(ImportStatus::valueArray());
            $currentStatus = strtolower($product->status ?? '');
            $currentStatus = $currentStatus === 'success' 
                ? ImportStatus::FINISHED->value  
                : $currentStatus;
            $newStatus = $validStatuses->contains($currentStatus)
                ? $currentStatus
                : ImportStatus::PENDING->value;

            // update the status, if one fails set the status to 
            // the default value
            Post::where('id', $post->id)
                ->update(['status' => $newStatus]);
        }
    }

    private function changeStatusToEnum(): void
    {
        // note: enums cannot be changed with migrations. 
        //       It has to be done with a DB statement
        $validEnumValues = ImportStatus::valueList("', '");
        $defaultValue = ImportStatus::PENDING->value;
        $query = "ALTER TABLE posts";
        $query .= "MODIFY COLUMN status enum('{$validEnumValues}') ";
        $query .= "DEFAULT '{$defaultValue}'";
        DB::statement($query);
    }
};

Update current references to the class constants to the enum value

A little drawback is that I can no longer refer to the values as ImportStatus::validating as that is now returning an enum instead of a string.

To get the value of an enum you have to call the value property on it: ImportStatus::validating->value

So change your codebase by replacing ImportStatus::validating to ImportStatus::validating->value

Git remove local branches that don’t exist remote

The quick way:

git branch --merged master | grep -v '^[ *]*master$' | xargs git branch -d
git remote prune origin

Use the following to have the branches displayed before you’re asked to delete them.

branches=$(git branch --merged master | grep -v '^[ *]*master$'); \
printf '\n\nBranches to be removed:\n---\n'; \
echo ${branches} | xargs -n1; \
printf '---\n\nRemove the branches above? [Ny] ' \
    &&  read shouldDelete \
    && [[ "${shouldDelete}" =~ [yY] ]] \
      && echo $branches | xargs git branch -d \
      || echo 'aborted' 

source: https://stackoverflow.com/a/16906759

Sort an array in javascript by property of one of the object elements

Say you have an array consisting of objects. And you want to order that array by one of the properties of these objects.

Take this array:

[
    {
        id: 4,
        title: "Lorem ipsum",
        created: "2020-04-10T14:59:00+00:00"
    },
    {
        id: 2,
        title: "Iptum quanto",
        created: "2020-05-29T13:17:48+00:00"
    },
    {
        id: 1,
        title: "Dolor samet",
        created: "2020-05-29T13:16:20+00:00"
    },
    {
        id: 3,
        title: "Tenari fluptum",
        created: "2020-05-29T13:08:39+00:00"
    }
]

Let’s sort it by the string created property of each element

const orderedChapters = chapters.sort((a, b) => {
  return a.created.localeCompare(b.created))
}

OR sort it by the numerical id property of each element

const orderedChapters = chapters.sort((a, b) => {
  return (a.id > b.id) ? 1 : ((b.id > a.id) ? -1 : 0)
}

POST request turns into GET request

I was firing login request to my API but I could not get it to return the correct response.

Upon investigation, it turns out that the API received a GET request while I issued a POST request with Postman.

The issue? I was sending the POST request to http while the API was listening on https!

It turns out that the request was forwarded to the https address as a GET request…

Solution: send requests to the https address