CodeIgniter Forums

Full Version: Entity cast-on-fill
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I was actually surprised that this wasn't already the case... I am curious what everyone thinks about casting entity values during fill(). Consider the following example:
PHP Code:
class Exercise extends \CodeIgniter\Entity
{
    protected 
$dates      = ['created_at''updated_at'];
    protected 
$casts      = ['amount' => 'int'];
    protected 
$attributes = ['name' => 'running'];
}

...

$exercise = new Exercise([
    
'amount' => $this->request->getPost('amount')
]);

(new 
ExerciseModel())->insert($exercise); 

I have defined the "amount" field in my casts to be an integer, but when I fill the new entity with form input it goes in as a string. Not a big deal, if I `var_dump($exercise->amount)` the cast will ensure it comes out as an integer. However, when the model tries to insert it the string version gets sent. Often this isn't an issue, but some databases might not make the conversion leaving you with a string in your database.

So two possible solutions: when fill() is called it runs the values through $cast to ensure they are always of the expected type. The other thought was to add casts to the model's classToArray() method (it already casts time values); this intentionally uses "toRawArray()" instead of "toArray()" to avoid the datamap and magic getters, but I don't see why it should avoid casting types.
I've always thought of casting as a convenience thing for the programmer working with the Entity. It has never defined the data itself. Here's some examples it was created to help with:

1. MySQL likes to return numbers as strings, but we want to be able to do strict type checks, so we cast as an int when we work with the value in our code. In this case MySQL wouldn't care so much about what it was during insert, though it's possible others might.
2. I have an array of meta data that I want to store with the value. Might be something that doesn't benefit being normalized because it's not used in relations in any way, or is a log of a JSON call, etc. The database may/may not support JSON columns, but the query builder doesn't understand them. So we serialize the value when saved to the database like we have always done. However, we would like to save ourself a step when working with the data, so we cast it as an array.

The second idea it makes a potential case for casting on the way in, but I think things might get tricky at the database level. Been a while since I've looked at that code, honestly. Could potentially work.

But basically, in my mind, casts have always been only on the way out as a convenience feature. If it doesn't cause more problems than it solves, I guess I don't have an issue.

Come to think of it, though, and a safer way to do it without modifying any code is simply to provide a setX() method that will cast it automatically on the way in.
(04-14-2020, 10:54 AM)kilishan Wrote: [ -> ]Come to think of it, though, and a safer way to do it without modifying any code is simply to provide a setX() method that will cast it automatically on the way in.

That would work but I guess what I'm after is a way to normalize data types in bulk. That approach would require a `setX()` method for every attribute and would duplicate the casts we have defined for the way out. For the moment I've written my own extension to `toRawArray()` like I outlined above.
PHP Code:
    /**
     * Force raw array output through casts.
     *
     * @param boolean $onlyChanged
     *
     * @return array
     */
    
public function toRawArray(bool $onlyChanged false): array
    {
        
$array parent::toRawArray($onlyChanged);
        
        if (empty(
$this->casts))
        {
            return 
$array;
        }

        
// Check each key for a cast
        
foreach ($array as $key => $value)
        {
            if (! empty(
$this->casts[$key]))
            {
                
$array[$key] = $this->castAs($value$this->casts[$key]);
            }
        }

        return 
$array;
    } 
Looking at the types of casting we provide, I can see a bulk-import like that having issues with casting to objects, datetime, timestamps, and potentially others, though. Without a lot of special case code I don't think it's a viable solution. Making a custom fill method on your Entity could be one solution for your bulk needs. I imagine it would need to check for field types that don't cast well to the database, etc.

Or, maybe I'm not thinking it all of the way through and missing a piece. That's happened before Smile