dimitriBouteille / wp-orm

WordPress ORM with Eloquent, an object-relational mapper that makes it enjoyable to interact with your database.
https://packagist.org/packages/dbout/wp-orm
MIT License
67 stars 5 forks source link

[BUG]: Attributes are not automatically cast to their column types #74

Open maxrice opened 1 month ago

maxrice commented 1 month ago

Describe the bug

AFAIK, Eloquent will cast attributes to the column type as defined in the table. When adding a custom model with say, an integer column, the attribute type when accessing it like $instance->attribute will always be a string instead of integer.

Steps to reproduce the issue

Create a custom model:

class Test extends AbstractModel {

    protected $table = 'test';
}

and a matching table:

CREATE TABLE test (
  PRIMARY KEY  (id),
  id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  test_count int(20) UNSIGNED DEFAULT 0 NOT NULL,
)

Insert a record into the table:

INSERT INTO test (test_count) VALUES (1234);

Then, load the model and dump the integer column:

$test = Test::find( 1 );
var_dump( $test->test_count );

And you'll get:

string(4) "1234"

Interestingly, if you add the column to the $casts property, like:

protected $casts = [
    'test_count' => 'integer',
];

then you get the expected behavior:

int(1234)

IMO that's an acceptable workaround if there's no way to have Eloquent automatically cast the column values, due to a conflict with $wpdb or something.

Your setup

dimitriBouteille commented 1 month ago

Hey @maxrice

Thanks for opening this issue. I did not know this feature of Eloquent, I have always used the $cast property.

Since this is a correction that may impact the functioning of the library, I prefer to fix this bug in the next major release: 4.0.0. I hope to release the 4.x.x in november.

As stated at the beginning of the issue, the easiest way is to go through the $casts property.

Best,

maxrice commented 1 month ago

@dimitriBouteille yeah it seems like it depends on the driver being used, based on https://github.com/laravel/framework/issues/11780, anyhow. So it might be worth some additional investigation when you start working on the next major release, but not a big deal to use the $casts property either. thanks!

dimitriBouteille commented 1 month ago

Hi @maxrice

I’m looking to fix this problem. However, I don’t see any reference in Laravel or Eloquent that automatically cast an attribute according to the type of column.

I tried to install a Laravel project to see if there is a difference, and laravel also uses the manual cast system.

When installing Laravel, the User class is automatically created and uses the casts function :

<?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;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

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

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

When I disable the cast of the email_verified_at property, it is a string type that is returned :

/** @var \App\Models\User $user */
$user = \App\Models\User::find(1);
dd($user, $user->email_verified_at, $user->getAttribute('email_verified_at'));

Capture d’écran du 2024-09-28 15-46-40

Do you have a link to documentation that talks about this automatic cast?

maxrice commented 1 month ago

@dimitriBouteille It doesn't seem to be mentioned explicitly in the docs, though I did find a few mentions in blog posts about how this is done "automatically". From re-reading https://github.com/laravel/framework/issues/11780, I think this happens in the database driver itself, not Laravel, so it probably can't be "fixed" in WP ORM; could consider adding a note to the docs about using the $casts property?