Fayçal Hammiche Fayçal Hammiche - 1 year ago 143
MySQL Question

Symfony Notice: unserialize(): Error at offset 36 of 40 bytes , moving from Mysql to PostgreSQL

I'm facing a problem while calling an api made in-house.
The log says :

[2017-02-07 16:04:39] doctrine.DEBUG: SELECT h0_.id AS id_0, h0_.hash AS hash_1, h0_.request AS request_2, h0_.options AS options_3, h0_.serialized_response_body AS serialized_response_body_4, h0_.response AS response_5, h0_.sent_at AS sent_at_6 FROM http_request h0_ WHERE h0_.hash = ? ["dd3e36a5a38618974cae2b45f9cb3a67"] []
[2017-02-07 16:04:39] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Notice: unserialize(): Error at offset 36 of 40 bytes" at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php line 57 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\ContextErrorException(code: 0): Notice: unserialize(): Error at offset 36 of 40 bytes at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php:57)"} []


I'm kinda lost >.<

EDIT :

In my controller, I submit a form containing several information in order to do the process.

/**
* @Rest\View(serializerGroups={"Review"})
* @Security("is_granted('ROLE_REVIEW_CREATE')")
*/
public function postAction(Request $request)
{
if ($this->getUser()->getBalance()->getTokens() == 0) {
throw new AccessDeniedException('The Review can not be created because you lack of tokens');
}

$review = new Review();
$review->setCreatedBy($this->getUser());

$reviewForm = $this->createForm(ReviewType::class, $review, [
'csrf_protection' => false,
]);
$reviewForm->submit($request->request->all());
if ($reviewForm->isSubmitted() && $reviewForm->isValid()) {
// If the user is in an organization, takes the organization analyzer environment, otherwise takes the user one.
$analyzerEnvironment = $this->getUser()->getCompany() ? $this->getUser()->getCompany()->getAnalyzerEnvironment() : $this->getUser()->getAnalyzerEnvironment();

$crawlingFlow = $this->get('app.manager.crawling_flow')->getCrawlingFlow();

$this->get('app.manager.review')->process(
$review,
$crawlingFlow,
$analyzerEnvironment
);

$reviewEncrypted = new ReviewEncrypted();
$reviewEncrypted->setReliability($review->getReliability());
$reviewEncrypted->setData($this->get('app.manager.review')->encrypt($review));
$reviewEncrypted->setCreatedBy($this->getUser());
$this->getDoctrine()->getManager()->persist($reviewEncrypted);
$this->getDoctrine()->getManager()->flush();
$review->setId($reviewEncrypted->getId());

if ($this->getUser()->getBalance()->getTokens() > 0) {
$this->getUser()->getBalance()->setTokens($this->getUser()->getBalance()->getTokens() - 1);
$this->getDoctrine()->getManager()->persist($this->getUser());
$this->getDoctrine()->getManager()->flush();
}

return $review;
}

return $reviewForm;
}


An when calling:

$crawlingFlow = $this->get('app.manager.crawling_flow')->getCrawlingFlow();

$this->get('app.manager.review')->process(
$review,
$crawlingFlow,
$analyzerEnvironment
);


Several services are called, they send requests, do stuff like storing in DB, etc ...

Question is, is there something I forgot to do when I changed the db (from mysql to postgresql), or is it something else ?

Where can possibly come the unserialize() error ? Why ?

Thanks for the help :p

EDIT 2:

Here where the error comes from according to the log "vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php line 57" :

/**
* {@inheritdoc}
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return serialize($value);
}

/**
* {@inheritdoc}
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}

$value = (is_resource($value)) ? stream_get_contents($value) : $value;
$val = unserialize($value);
if ($val === false && $value !== 'b:0;') {
throw ConversionException::conversionFailed($value, $this->getName());
}

return $val;
}

Answer Source

There is one important difference between text of MySQL and PostgreSQL, and may be it's your case. PostgreSQL do not allow \0 in text field. So, on prepare stage pg driver cut string on first \0.

Unfortunately, serialize use \0 symbol internally.

class A {
  private $a = 'a';
  private $b = 'b';
}

$a = new A();

var_export(serialize($a));

will show 'O:1:"A":2:{s:4:"' . "\0" . 'A' . "\0" . 'a";s:1:"a";s:4:"' . "\0" . 'A' . "\0" . 'b";s:1:"b";}'

MySQL insert of this sting will work fine: INSERT INTO test1 VALUES ('O:1:\"A\":2:{s:4:\"\0A\0a\";s:1:\"a\";s:4:\"\0A\0b\";s:1:\"b\";}')

But Pg driver silently cut string and execute
LOG: execute pdo_stmt_00000001: INSERT INTO test1 VALUES ($1)
DETAIL: parameters: $1 = 'O:1:"A":2:{s:4:"'
or INSERT INTO test1 VALUES ('O:1:"A":2:{s:4:"') with prepare emulation.

As you can see - result is cut string and unserialize can't restore it.

Solution: use bytea datatype for Postgresql or add some kind of encode/decode of serialized string.

UPD: About object datatype in Doctrine http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#object

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download