skip to Main Content

I have a varbinary(200) NOT NULL field on a table.

Let’s say the table is called users and the field is called secret.

In CakePHP 3.X, accessing

$user->secret;

after retrieving one row of the table would give me a string.

In CakePHP 4.X, instead, accessing the same property gives me a resource.
If I do

stream_get_contents($user->secret);

I can then access the string value, but why do I have do that now? I couldn’t find anything specific in the CakePHP changelogs pertaining to varbinary fields.

It’s quite annoying now to have to go through all the places in my code where I access that property, and add stream_get_contents() everywhere.

Is there any way I can change my model instead so that the behavior remains consistent with what it was before?

2

Answers


  1. I’m not familiar with the cake fw itself, but it looks like you can use the accessors in entities so you can alter the return when retrieving the property:

    namespace AppModelEntity;
    
    use CakeORMEntity;
    
    class Test extends Entity
    {
        protected function _getSecret($secret)
        {
            return stream_get_contents($secret);
        }
    }
    
    echo $test->secret; // returns FOO instead of foo
    echo $test->get('secret'); // returns FOO instead of foo
    

    Example taken from the link above.

    Login or Signup to reply.
  2. Binary data columns were always meant to return resources in 3.x, but you’re probably using a very old 3.x version, where VARBINARY wasn’t recognized (pre 3.6.12), resulting in it to default to treating it as a string.

    Binary data columns are generally returned as resources in order to unify the behavior across the different supported DBMS and their various binary data types, as for some of them PDO returns the data as strings, and for others it returns it as resource handles.

    Using entity accessors as suggested by @manuel-guzman is a way to always retrieve strings, but be aware that accessors will be used when persisting entities, meaning they will be invoked when saving, and the data they return is what will end up in the database!

    Another option would be result formatters:

    // in your `UsersTable` class
    
    public function beforeFind(
        CakeEventEventInterface $event,
        CakeORMQuery $query,
        ArrayObject $options,
        $primary
    ) {
        $query->formatResults(function (CakeCollectionCollectionInterface $results) {
            return $results->map(function ($row) {
                if (
                    isset($row['secret']) &&
                    is_resource($row['secret'])
                ) {
                    $row['secret'] = stream_get_contents($row['secret']);
                }
                
                return $row;
            });
        });
    }
    

    See also

    And there’s also custom database types that could be used to always return the data as strings when reading from the database using the query builder:

    // in `src/Database/Type/StringBinaryType.php`
    
    namespace AppDatabaseType;
    
    use CakeDatabaseDriverInterface;
    use CakeDatabaseTypeBinaryType;
    use InvalidArgumentException;
    use PDO;
    
    class StringBinaryType extends BinaryType
    {
        public function toPHP($value, DriverInterface $driver)
        {
            if ($value === null) {
                return null;
            }
            
            if (is_string($value)) {
                return $value;
            }
            
            if (is_resource($value)) {
                return stream_get_contents($value);
            }
            
            throw new InvalidArgumentException(sprintf(
                'Cannot convert value of type `%s` to string',
                getTypeName($value)
            ));
        }
    }
    
    // in `config/bootstrap.php`
    
    use CakeDatabaseTypeFactory;
    
    TypeFactory::map('stringBinary', AppDatabaseTypeStringBinaryType::class);
    
    // in your `UsersTable` class
    
    public function initialize(): void
    {
        // ...
        
        $this->getSchema()->setColumnType('secret', 'stringBinary');
    }
    

    See also

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search