英文:
VARBINARY field in CakePHP 4
问题
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?
英文:
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?
答案1
得分: 1
以下是您要翻译的内容:
"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(
\Cake\Event\EventInterface $event,
\Cake\ORM\Query $query,
\ArrayObject $options,
$primary
) {
$query->formatResults(function (\Cake\Collection\CollectionInterface $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
- Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks > beforeFind
- Cookbook > Database Access & ORM > Query Builder > Selecting Data > Adding Calculated Fields
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 App\Database\Type;
use Cake\Database\DriverInterface;
use Cake\Database\Type\BinaryType;
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 an InvalidArgumentException(sprintf(
'Cannot convert value of type `%s` to string',
getTypeName($value)
));
}
}
// in `config/bootstrap.php`
use Cake\Database\TypeFactory;
TypeFactory::map('stringBinary', \App\Database\Type\StringBinaryType::class);
// in your `UsersTable` class
public function initialize(): void
{
// ...
$this->getSchema()->setColumnType('secret', 'stringBinary');
}
See also
英文:
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(
\Cake\Event\EventInterface $event,
\Cake\ORM\Query $query,
\ArrayObject $options,
$primary
) {
$query->formatResults(function (\Cake\Collection\CollectionInterface $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
- Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks > beforeFind
- Cookbook > Database Access & ORM > Query Builder > Selecting Data > Adding Calculated Fields
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 App\Database\Type;
use Cake\Database\DriverInterface;
use Cake\Database\Type\BinaryType;
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 Cake\Database\TypeFactory;
TypeFactory::map('stringBinary', \App\Database\Type\StringBinaryType::class);
// in your `UsersTable` class
public function initialize(): void
{
// ...
$this->getSchema()->setColumnType('secret', 'stringBinary');
}
See also
答案2
得分: 0
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 App\Model\Entity;
use Cake\ORM\Entity;
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.
英文:
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 App\Model\Entity;
use Cake\ORM\Entity;
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论