Skip to content

Introduce PHP::PhpObject to handle repeating stdClass with different property sets #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions lib/php_serialize.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'stringio'
require 'ostruct'

module PHP
class StringIOReader < StringIO
Expand All @@ -15,6 +16,16 @@ def read_until(char)
end
end

# Represents a serialized PHP object
class PhpObject < OpenStruct
# @return [String] The name of the original PHP class
attr_accessor :_php_classname

def to_assoc
each_pair.to_a
end
end

# Returns a string representing the argument in a form PHP.unserialize
# and PHP's unserialize() should both be able to load.
#
Expand Down Expand Up @@ -79,7 +90,8 @@ def PHP.serialize(var, assoc = false) # {{{
if var.respond_to?(:to_assoc)
v = var.to_assoc
# encode as Object with same name
s << "O:#{var.class.to_s.bytesize}:\"#{var.class.to_s.downcase}\":#{v.length}:{"
class_name = var.respond_to?(:_php_classname) ? var._php_classname : var.class.to_s.downcase
s << "O:#{class_name.bytesize}:\"#{class_name}\":#{v.length}:{"
v.each do |k,v|
s << "#{PHP.serialize(k.to_s, assoc)}#{PHP.serialize(v, assoc)}"
end
Expand Down Expand Up @@ -139,8 +151,8 @@ def PHP.serialize_session(var, assoc = false) # {{{
# to be the class itself; i.e. something you could call .new on.
#
# If it's not found in 'classmap', the current constant namespace is searched,
# and failing that, a new Struct(classname) is generated, with the arguments
# for .new specified in the same order PHP provided; since PHP uses hashes
# and failing that, a new PHP::PhpObject (subclass of OpenStruct) is generated,
# with the properties in the same order PHP provided; since PHP uses hashes
# to represent attributes, this should be the same order they're specified
# in PHP, but this is untested.
#
Expand Down Expand Up @@ -209,7 +221,8 @@ def PHP.do_unserialize(string, classmap, assoc)
when 'O' # object, O:length:"class":length:{[attribute][value]...}
# class name (lowercase in PHP, grr)
len = string.read_until(':').to_i + 3 # quotes, seperator
klass = string.read(len)[1...-2].capitalize.intern # read it, kill useless quotes
klass_in_php = string.read(len)[1...-2]
klass = klass_in_php.capitalize.intern # read it, kill useless quotes

# read the attributes
attrs = []
Expand All @@ -233,9 +246,10 @@ def PHP.do_unserialize(string, classmap, assoc)
classmap[klass] = val = Module.const_get(klass)

val = val.new
rescue NameError # Nope; make a new Struct
classmap[klass] = val = Struct.new(klass.to_s, *attrs.collect { |v| v[0].to_s })
val = val.new
rescue NameError # Nope; make a new PhpObject
val = PhpObject.new.tap { |php_obj|
php_obj._php_classname = klass_in_php.to_s
}
end
end

Expand Down
27 changes: 21 additions & 6 deletions test/php_serialize_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,25 @@ def test_sessions
end
end

def test_new_struct_creation
assert_nothing_raised do
phps = 'O:8:"stdClass":2:{s:3:"url";s:17:"/legacy/index.php";s:8:"dateTime";s:19:"2012-10-24 22:29:13";}'
PHP.unserialize(phps)
end
end
def test_creates_php_object_instance_if_class_undefined
assert_nothing_raised do
phps = 'O:8:"stdClass":2:{s:3:"url";s:17:"/legacy/index.php";s:8:"dateTime";s:19:"2012-10-24 22:29:13";}'
unserialized = PHP.unserialize(phps)

assert_kind_of PHP::PhpObject, unserialized
assert_equal "/legacy/index.php", unserialized.url

reserialized = PHP.serialize(unserialized)
assert_equal phps, reserialized
end
end

def test_same_classname_appears_twice
assert_nothing_raised do
# can be generated with:
# serialize([(object)["foo" => 1], (object)["bar" => 2]])
phps = 'a:2:{i:0;O:8:"stdClass":1:{s:3:"foo";i:1;}i:1;O:8:"stdClass":1:{s:3:"bar";i:2;}}'
PHP.unserialize(phps)
end
end
end