I need to serialize in Perl with PHP format

Viewed 116

I can serialize a hash with the PHP serialization format using PHP::Serialization.

$result = serialize({ a => 1111, b => 2222})
# IT GIVES ME 'a:2:{s:1:"a";i:1111;s:1:"b";i:2222;}';

BUT when values are strings I need them treated as strings, even if they look as numbers or dates.

$result = serialize({ a => '1111', b => '2222'});
# IT GIVES ME THE SAME 'a:2:{s:1:"a";i:1111;s:1:"b";i:2222;}';

And this is what I need:

# 'a:2:{s:1:"a";s:4:"1111";s:1:"b";s:4:"2222";}'

OK, Perl is very loosy about types, but if I say something is a string... shouldn't it be treated as a string?

PHP::Serialization has an option to treat double or float as strings, but integers, dates and so on, even when quoted recover its Perl type. I tried double and single quotes, also q(), qq(), qw(), but no luck. Even tricks like "1111" . "".

I can't find an alternative in CPAN. Do I need to rewrite PHP::Serialization?

1 Answers

You don't need to write your own parser. You can subclass PHP::Serialization. Or at least you can try.

My below implementation can be used as a drop-in for your example, but it has limits.

The module looks like it allows subclassing at first glance because the serialize and deserialize functions use __PACKAGE__ to instantiate, and new uses $class too. But at closer inspection, there are a couple of lexical variables in the file scope that are basically isolated from a subclass. Therefore, some functionality is lost. You also don't inherit package variables like @EXPORT_OK, and in general the Exporter does not play totally well with OO code.

My code can be used as a drop-in replacement for your specific example, and I believe it can serialise data structures as long as they are not sorted. If you need $sorthash (which is the $shash arg to encode) you get into trouble, because the lexical one in the subclass is different from the one in the parent class, and there is no sane way to get it.

To make this work, we have to redefine serialize and export it.

I've put a very naive hack in to just treat everything that isn't a data structure or an object as a string, basically eliminating numbers.

package PHP::Serialization::MoreStringent;
use strict;
use warnings;

use Scalar::Util qw/blessed/;
use Carp qw(confess);

use parent 'PHP::Serialization';

# we need this so the non-OO functions can be exported
our @EXPORT_OK = qw(serialize);
sub serialize   { __PACKAGE__->new->encode(@_) }

# this thing is bad, because it's now lexical in our subclass - so it breaks
my $sorthash;

# this is mostly a copy of the original code
sub encode {
    my ($self, $val, $iskey, $shash) = @_;
    $iskey=0 unless defined $iskey;
    $sorthash=$shash if defined $shash;

    if ( ! defined $val ) {
        return $self->_encode('null', $val);
    }
    elsif ( blessed $val ) {
        return $self->_encode('obj', $val);
    }
    elsif ( ! ref($val) ) {
        # very naive hack to make all other things into string
        return $self->_encode('string', $val);
    }
    else {
        my $type = ref($val);
        if ($type eq 'HASH' || $type eq 'ARRAY' ) {
            return $self->_sort_hash_encode($val) if (($sorthash) and ($type eq 'HASH'));
            return $self->_encode('array', $val);
        }
        else {
            confess "I can't serialize data of type '$type'!";
        }
    }
}

1;

Now we can run this with the following test, and it at least produces the output you want.

use Test::More;
use PHP::Serialization::MoreStringent 'serialize';

is serialize({ a => 1111, b => 2222}), 'a:2:{s:1:"a";s:4:"1111";s:1:"b";s:4:"2222";}';

done_testing;

I have not tested it with data structures because I have no idea what they should look like in PHP's serialisation format. I have deliberately removed the deserialisation because that makes heavy use of this $sorthash variable, so we have basically broken it. I think.

However the concept should be clear.


As an alternative, could you just post-process the output and regex the quotes in?

Related