michaeldyrynda / laravel-model-uuid

This package allows you to easily work with UUIDs in your Laravel models
MIT License
442 stars 46 forks source link

Ramsey\Uuid\Exception\InvalidUuidStringException Invalid UUID string: 0 #103

Closed wilocampo closed 3 years ago

wilocampo commented 3 years ago

I have an issue with a newly-created project.

When I save a new model, this error occurs but the record is saved in the database.

Ramsey\Uuid\Exception\InvalidUuidStringException Invalid UUID string: 0

This is my migration:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->efficientUuid('id')->primary();
            $table->string('name');
            $table->string('description')->nullable();
            $table->efficientUuid('created_by')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('items');
    }
}

This is my Item model:


<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/* UUID */
use Dyrynda\Database\Support\GeneratesUuid;
use Dyrynda\Database\Casts\EfficientUuid;
/* Record Signature*/
use App\Traits\RecordSignature;

class Item extends Model
{
    use HasFactory;
    use GeneratesUuid;
    use RecordSignature;

    protected $fillable = ['name','description'];

    protected $uuidVersion = 'ordered';

    protected $casts = [
        'id' => EfficientUuid::class,
        'created_by' => EfficientUuid::class,
    ];

    public function uuidColumns(): array
    {
        return ['id','created_by'];
    }
}

My RecordSignature Trait:


<?php

namespace App\Traits;

trait RecordSignature
{
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->created_by = \Auth::User()->uuid;
        });
    }
}

This is my livewire component:


<?php

namespace App\Http\Livewire\Items;

use Livewire\Component;
use App\Models\Item;

class Create extends Component
{
    public $name = 'Carnacion Evap';
    public $description = 'Para masarap';

    public function render()
    {
        return view('livewire.items.create');
    }

    public function save()
    {
        $item = new Item;

        $data = $this->validate([
            'name' => 'required',
            'description' => 'min:1'
        ]);

        $item->create([
            'name' => $data['name'],
            'description' => $data['description']
        ]);

    }
}

and my blade if it helps:

<div>
    <div class="flex flex-wrap">
        <div class="mx-auto lg:w-8/12 px-4">
            <div class="relative flex flex-col min-w-0 break-words w-full mb-6 shadow-lg rounded-lg bg-blueGray-100 border-0">
                <form wire:submit.prevent="save">
                    <div class="rounded-t bg-white mb-0 px-6 py-6">
                        <div class="text-center flex justify-between">
                            <h6 class="text-blueGray-700 text-xl font-bold">
                                Items
                            </h6>
                            <button class="bg-pink-500 text-white active:bg-pink-600 font-bold uppercase text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 ease-linear transition-all duration-150" type="submit">
                                Save
                            </button>
                        </div>
                    </div>
                    <div class="flex-auto px-4 lg:px-10 py-10 pt-0">

                        <h6 class="text-blueGray-400 text-sm mt-3 mb-6 font-bold uppercase">
                            Item Information
                        </h6>
                        <div class="flex flex-wrap">
                            <div class="w-full lg:w-6/12 px-4">
                                <div class="relative w-full mb-3">
                                    <label class="block uppercase text-blueGray-600 text-xs font-bold mb-2" htmlfor="grid-password">
                                        Name
                                    </label>
                                    <input wire:model.lazy="name" type="text" class="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150" value="{{ $name }}" placeholder="Item Name">
                                </div>
                            </div>
                            <div class="w-full lg:w-6/12 px-4">
                                <div class="relative w-full mb-3">
                                    <label class="block uppercase text-blueGray-600 text-xs font-bold mb-2" htmlfor="grid-password">
                                        description
                                    </label>
                                    <input wire:model.lazy="description" type="text" class="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150" value="{{ $description }}" placeholder="Item description">
                                </div>
                            </div>
                        </div>

                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

I cannot find where the issue lies, because I have tried this, third time now on a new laravel installation, this time with only the laravel-efficient-uuid and laravel-model-uuid packages installed (besides jetstream).

Please help.

This is my composer.json


{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "require": {
        "php": "^7.3|^8.0",
        "dyrynda/laravel-efficient-uuid": "^4.3",
        "dyrynda/laravel-model-uuid": "^6.4",
        "fideloper/proxy": "^4.4",
        "fruitcake/laravel-cors": "^2.0",
        "guzzlehttp/guzzle": "^7.0.1",
        "laravel/framework": "^8.12",
        "laravel/jetstream": "^2.3",
        "laravel/sanctum": "^2.6",
        "laravel/tinker": "^2.5",
        "livewire/livewire": "^2.0"
    },
    "require-dev": {
        "facade/ignition": "^2.5",
        "fakerphp/faker": "^1.9.1",
        "laravel/sail": "^1.0.1",
        "mockery/mockery": "^1.4.2",
        "nunomaduro/collision": "^5.0",
        "phpunit/phpunit": "^9.3.3"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi",
            "@php artisan vendor:publish --force --tag=livewire:assets --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    },
    "extra": {
        "laravel": {
            "dont-discover": []
        }
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}
josuelrocha commented 3 years ago

In your User model add:

use Ramsey\Uuid\Uuid;

protected $appends = [
    'uuid',
];

public function getUuidAttribute()
{
    return Uuid::fromBytes($this->attributes['uuid']);
}

or convert the uuid binary to string directly in use:

use Ramsey\Uuid\Uuid;

static::creating(function ($model) {
    $model->created_by = Uuid::fromBytes(\Auth::User()->uuid);
});
wilocampo commented 3 years ago

In your User model add:

use Ramsey\Uuid\Uuid;

protected $appends = [
    'uuid',
];

public function getUuidAttribute()
{
    return Uuid::fromBytes($this->attributes['uuid']);
}

Hi,

Thanks for the response.

I tried it but still same error occurs.

This is my User model:

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;
/* UUID */
use Dyrynda\Database\Support\GeneratesUuid;
use Dyrynda\Database\Casts\EfficientUuid;
use Ramsey\Uuid\Uuid;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;
    use GeneratesUuid;

    protected $uuidVersion = 'ordered';

    public function getUuidAttribute()
    {
        return Uuid::fromBytes($this->attributes['uuid']);
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'uuid' => EfficientUuid::class,
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
        'uuid',
    ];
}

And my user table migration:


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->efficientUuid('uuid')->index();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->text('profile_photo_path')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

And regarding this

or convert the uuid binary to string directly in use:

use Ramsey\Uuid\Uuid;

static::creating(function ($model) {
    $model->created_by = Uuid::fromBytes(\Auth::User()->uuid);
});

Setting the created_by in my trait works, so I did not converted the uuid to bytes. It saves data perfectly on my table.

I don't know where the issue lies, but after writing to the database, it produces an error

Below is the screenshot of the items table. The data is actually saved correctly. But after that, there is an error.

image

image

michaeldyrynda commented 3 years ago

Can you create a new repository with the bare minimum to reproduce this?

josuelrocha commented 3 years ago

try calling the toString() method

in method 1:

public function getUuidAttribute()
{
    return Uuid::fromBytes($this->attributes['uuid'])->toString();
}

in method 2:

static::creating(function ($model) {
    $model->created_by = Uuid::fromBytes(\Auth::User()->uuid)->toString();
});
wilocampo commented 3 years ago

I finally fixed it. The problem was very simple.

The issue was Eloquent assumes the primary key to be incrementing. Since I am using id as uuid as my primary key in my Item model, adding the following line to the model removes the error.

public $incrementing = false;

I would be helpful if we can add this to the docs for those who will use UUID as primary key.

Thank you everyone.

michaeldyrynda commented 3 years ago

I've added a note to the readme about the above. Thanks @wilocampo!

PureLines commented 3 years ago

I've added a note to the readme about the above. Thanks @wilocampo!

Thank you for this.

Could you please also explain why it's not recommended to use efficient UUID as primary key? Because I'm starting to use UUID exactly in this way. To achieve the ability to create models on multiple independent sources and sync it then.

michaeldyrynda commented 3 years ago

If you've got multiple database servers and you're syncing across, you typically want to offset the auto incrementing IDs by however many servers you have anyway. For simplicity, if you have two servers, you'd have one auto incrementing odd values (1, 3, 5, etc.) and the other even values (2, 4, 6, etc.) so that you don't have primary key collisions, handles multi-master, etc.

Using a combination of incrementing ID and UUID provides for more efficient queries at scale (from the readme) - https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/