gabordemooij / redbean

ORM layer that creates models, config and database on the fly
https://www.redbeanphp.com
2.31k stars 280 forks source link

Issue with booleans after update 5.4 -> 5.7? #932

Closed gabordemooij closed 1 year ago

gabordemooij commented 1 year ago

I updated an old app from

php 5.4.16

postgres 9.2

redbean 5.3.4

to

php 8.2.7

postgres 15.3

redbean 5.7.4

And now I am having a problem with boolean types.

For example, with “$customer = R::dispense('customers')->getById($id);”. It’s a simple R::getRow behind.

In my “$customer” array, I had “true/false” values for the boolean types before. Now I am getting “0/1” as string.

Has there been a change in behavior in this regard ? Is there a way (some configuration) to change it ?

gabordemooij commented 1 year ago

Hi,

I tried it myself but unless I use R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE ); I always get strings for booleans, same for me in RedBeanPHP 5.3.4 as in 5.7.4.

Maybe some of your configuration was not properly ported?

cheers, Gabor

xschmets commented 1 year ago

Hello,

Thanks for investigating.

I didn't changed a single line of code in the Redbean configuration or in the model/query files.

The old and new code :

R::setup(DB_DRIVER . ':host=' . DB_HOST . ';dbname=' . DB_NAME, DB_UID, DB_PWD);
R::freeze(true);
R::useWriterCache(true);
R::exec('set client_encoding to utf8');
$isConnected = R::testConnection();

And a model file for example :

<?php

use RedBeanPHP\R;

class Model_languages extends Model_base {

  public function getById($id) {
    return R::getRow('
      SELECT A.id AS a_id, A.key AS a_key, A.value AS a_value, A.default AS a_default
      FROM languages AS A
      WHERE A.id = :id ;
    ',
    array(
      ':id' => $id
    ));
  }

A call :

$language = R::dispense('languages')->getById($id);

That's it, nothing else.

And if I do a json_encode of the result/array with the old app/packages, I get true/false for boolean fields, 0/1 as strings with the new app/packages.

I have never used parameters like

R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE );

I just did, nothing changes, still 0/1 strings.

The system is different, CentOS 7 before, Debian 12 now, but I doubt it's relevant.

Lynesth commented 1 year ago

What are the values like before you json_encode the array? Are they the same?

xschmets commented 1 year ago

Hello,

Old app/packages (CentOS7) :

$rule = R::dispense('rules')->getById($ruleId);
$app->log->error(print_r($rule, true));
$app->log->error(json_encode($rule));
Array (     [a_id] => 61     [a_name] => ddd     [b_id] => 3     [b_name] => Facture     [a_approval_threshold] => 1     [a_auto_approval] => 1     [a_timestamp] => 2023-08-29 09:40:26.203262     [a_companies_ids] => 1     [a_companies_codes] => DEFAUT )

{"a_id":"61","a_name":"ddd","b_id":"3","b_name":"Facture","a_approval_threshold":"1","a_auto_approval":true,"a_timestamp":"2023-08-29 09:40:26.203262","a_companies_ids":"1","a_companies_codes":"DEFAUT"}

New app/packages (Debian12) :

$rule = R::dispense('rules')->getById($ruleId);
$this->get('log')->error(print_r($rule, true));
$this->get('log')->error(json_encode($rule));
Array (     [a_id] => 71     [a_name] => ddd     [b_id] => 3     [b_name] => Facture     [a_approval_threshold] => 1     [a_auto_approval] => 1     [a_timestamp] => 2023-08-29 09:45:22.9765     [a_companies_ids] => 1     [a_companies_codes] => DEFAUT )

{"a_id":"71","a_name":"ddd","b_id":"3","b_name":"Facture","a_approval_threshold":"1","a_auto_approval":"1","a_timestamp":"2023-08-29 09:45:22.9765","a_companies_ids":"1","a_companies_codes":"DEFAUT"}

The field "a_auto_approval" is a boolean in PostgreSQL.

Lynesth commented 1 year ago

print_r doesn't show the difference between integers/strings/booleans but given that json_encode turns that field into true I will assume it is indeed a boolean in the first example (old app).

A few things seem to be the cause of the discrepancy you're observing.

First of all, stringifyFetches (therefore PDO::ATTR_STRINGIFY_FETCHES) was already in use and set to true in RedBean 5.3.4.

Secondly, it seems that the PostgreSQL PDO driver has always been returning actual PHP boolean type for boolean values.

And lastly, before PHP 8.1, using PDO::ATTR_STRINGIFY_FETCHES wouldn't actually turn booleans into strings (source)

PDO::ATTR_STRINGIFY_FETCHES now stringifies values of type bool to "0" or "1". Previously bools were not stringified.

Those are most likely the reason why you were getting booleans before.


However, using R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE ); should fix your issue. Could you make a very small script where you setup the connection, set stringifyFetches to false and retrieve the values from your database ? Oh, and please use var_dump instead of print_r so that we can check out actual data types.

xschmets commented 1 year ago

Big thank you for all these informations.

I was doing this :

R::setup(DB_DRIVER . ':host=' . DB_HOST . ';dbname=' . DB_NAME, DB_UID, DB_PWD);
R::freeze(true);
R::useWriterCache(true);
R::exec('set client_encoding to utf8');
$isConnected = R::testConnection();

R::getDatabaseAdapter()->getDatabase()->stringifyFetches(false);

Until I read this "Important! Note, this method only works if you set the value BEFORE the connection has been establish". https://redbeanphp.com/api/classes/RedBeanPHP.Driver.RPDO.html#method_stringifyFetches

So, calling this just after the setup worked :

R::setup(DB_DRIVER . ':host=' . DB_HOST . ';dbname=' . DB_NAME, DB_UID, DB_PWD);
R::getDatabaseAdapter()->getDatabase()->stringifyFetches(false);

R::freeze(true);
R::useWriterCache(true);
R::exec('set client_encoding to utf8');
$isConnected = R::testConnection();

I have now my booleans like in the old app ;-)

{"a_id":74,"a_name":"qqq","b_id":3,"b_name":"Facture","a_approval_threshold":1,"a_auto_approval":true,"a_timestamp":"2023-08-29 15:22:57.774179","a_companies_ids":"1","a_companies_codes":"DEFAUT"}

Again, thank you both and have a nice day.

Xavier

Lynesth commented 1 year ago

No worries, glad it worked out for you.

Just be mindful that now you're also getting actual integers (and other types) for some fields, as you can see in the json you last printed :)

xschmets commented 1 year ago

Yes I saw but it's not a problem. Thank you ;-)