NAME

SOAP::Data::ComplexType - An easy interface for creating and implementing infinitely complex SOAP::Data objects

SYNOPSIS

package My::SOAP::Data::ComplexType::Foo;
use strict;
use warnings;
use SOAP::Data::ComplexType;
use vars qw(@ISA);
@ISA = qw(SOAP::Data::ComplexType);

use constant OBJ_URI    => 'http://foo.bar.baz';
use constant OBJ_TYPE   => 'ns1:myFoo';
use constant OBJ_FIELDS => {
	field1              => ['string', undef, undef],
	field2              => ['int', undef, undef],
	field3              => ['xsd:dateTime', undef, undef]
};

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $data = shift;
	my $obj_fields = shift;
	$obj_fields = defined $obj_fields && ref($obj_fields) eq 'HASH' ? {%{$obj_fields}, %{+OBJ_FIELDS}} : OBJ_FIELDS;
	my $self = $class->SUPER::new($data, $obj_fields);
	return bless($self, $class);
}

package My::SOAP::Data::ComplexType::Bar;
use strict;
use warnings;
use SOAP::Data::ComplexType;
use vars qw(@ISA);
@ISA = qw(SOAP::Data::ComplexType);

use constant OBJ_URI    => 'http://bar.baz.uri';
use constant OBJ_TYPE   => 'ns1:myBar';
use constant OBJ_FIELDS => {
	val1                => ['string', undef, undef],
	val2                => [
		[
			My::SOAP::Data::ComplexType::Foo::OBJ_TYPE,
			My::SOAP::Data::ComplexType::Foo::OBJ_FIELDS
		],
		My::SOAP::Data::ComplexType::Foo::OBJ_URI, undef
	]
};

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $data = shift;
	my $obj_fields = shift;
	$obj_fields = defined $obj_fields && ref($obj_fields) eq 'HASH' ? {%{$obj_fields}, %{+OBJ_FIELDS}} : OBJ_FIELDS;
	my $self = $class->SUPER::new($data, $obj_fields);
	return bless($self, $class);
}

########################################################################
package main;

my $request_obj = My::SOAP::Data::ComplexType::Bar->new({
	val1    => 'sometext',
	val2    => {
		field1  => 'moretext',
		field2  => 12345,
		field3  => '2005-10-26T12:00:00.000Z'
	}
});
print $request_obj->as_xml_data;

use SOAP::Lite;
my $result = SOAP::Lite
		->uri($uri)
		->proxy($proxy)
		->somemethod(\SOAP::Data->value($request_obj->as_soap_data))
		->result;
		
#assuming the method returns an object of type Foo...
if (ref($result) eq 'Foo') {
	my $result_obj = My::SOAP::Data::ComplexType::Foo->new($result);
	print "$_=".$result_obj->$_."\n" foreach keys %{+My::SOAP::Data::ComplexType::Foo::OBJ_FIELDS};
}

ABSTRACT

SOAP::Data::ComplexType defines a structured interface to implement classes that represent infinitely complex SOAP::Data objects. Object instances can dynamically generate complex SOAP::Data structures or pure XML as needed. Fields of an object may be easily accessed by making a method call with name of the field as the method, and field values can be changed after object construction by using the same method with one parameter.

Blessed objects returned by a SOAP::Lite method's SOAP::SOM->result may be used to reconstitute the object back into an equivalent ComplexType, thus solving shortcomings of SOAP::Lite's handling of complex types and allowing users to access their objects in a much more abstract and intuive way. This is also exceptionally useful for applications that need use SOAP result objects in future SOAP calls.

DESCRIPTION

This module is intended to make it much easier to create complex SOAP::Data objects in an object-oriented class-structure, as users of SOAP::Lite must currently craft SOAP data structures manually. It uses SOAP::Data::Builder internally to store and generate object data.

I hope this module will greatly improve productivity of any SOAP::Lite programmer, especially those that deal with many complex datatypes or work with SOAP apps that implement inheritance.

IMPLEMENTATION

Creating a SOAP ComplexType class

Every class must define the following compile-time constants:

OBJ_URI:   URI specific to this complex type
OBJ_TYPE:  namespace and type of the complexType (formatted like 'myNamespace1:myDataType')
OBJ_FIELDS: hashref containing name => arrayref pairs; see L<ComplexType field definitions>

When creating your constructor, if you plan to support inheritance, you must perform the following action:

my $obj_fields = $_[1];	#second param from untouched @_
$obj_fields = defined $obj_fields && ref($obj_fields) eq 'HASH' ? {%{$obj_fields}, %{+OBJ_FIELDS}} : OBJ_FIELDS;
my $self = $class->SUPER::new($data, $obj_fields);

which insures that you support child class fields and pass a combination of them and your fields to the base constructor. Otherwise, you can simply do the following:

my $self = $class->SUPER::new($data, OBJ_FIELDS);

(Author's Note: I don't like this kludgy constructor design, and will likely change it in a future release)

ComplexType field definitions

When defining a ComplexType field's arrayref properties, there are 4 values you must specify within an arrayref:

type: (simple) SOAP primitive datatype, OR (complex) arrayref with [type, fields] referencing another ComplexType
uri:  specific to this field
attr: hashref containing any other SOAP::Data attributes

So, for example, given a complexType 'Foo' with

object uri='http://foo.bar.baz', 
object type='ns1:myFoo'

and two fields (both using simple SOAP type formats)

field1: type=string, uri=undef, attr=undef
field2: type=int, uri=undef, attr=undef

we would define our class exactly as seen in the SYNOPSYS for package My::SOAP::Data::ComplexType::Foo.

The second form of the type field may be an arrayref with the following elements:

type
fields hashref

So, for example, given a complexType 'Bar' with

object uri='http://bar.baz.uri', 
object type='ns1:myBar'

and two fields (one using simple SOAP type, the other using complexType 'myFoo')

field1: type=string, uri=undef, attr=undef
field2: type=myFoo, uri=undef, attr=undef

we would define our class exactly as seen in the SYNOPSYS for package My::SOAP::Data::ComplexType::Bar.

Class Methods

My::SOAP::Data::ComplexType::Example->new( HASHREF )

Constructor. Expects HASH ref (or reference to blessed SOAP::SOM->result object).

An example might be:

{ keya => { subkey1 => val1, subkey2 => val2 }, keyb => { subkey3 => val3 } }

When you have a ComplexType that allows for multiple elements of the same name (i.e. xml attribute maxOccurs > 1), use the following example form for simpleType values:

{ keya => [ val1, val2, val3 ] }

or, for complexType values:

{ keya => [ {key1 => val1}, {key2 => val2}, {key3 => val3} ] }

Object Methods

$obj->get_elem( NAME )

Returns the value of the request element. If the element is not at the top level in a hierarchy of ComplexTypes, this method will recursively parse through the entire datastructure until the first matching element name is found.

If you wish to get a specific element nested deeply in a ComplexType hierarchy, use the following format for the NAME parameter:

PATH/TO/YOUR/NAME

This example would expect to find the element in the following hierarchy:

<PATH>
	<TO>
		<YOUR>
			<NAME>
				value
			</NAME>
		</YOUR>
	</TO>
</PATH>

$obj->set_elem ( NAME, NEW_VALUE )

Sets the value of the element NAME to the value NEW_VALUE. Rules for what may be used for valid NAME parameters and how they are used are explained in documentation for get_elem object method.

$obj->FIELDNAME( [ NEW_VALUE ] )

Returns (or sets) the value of the given FIELDNAME field in your object. NEW_VALUE is optional, and changes the current value of the object.

$obj->as_soap_data

Returns all data as a list of SOAP::Data objects.

$obj->as_xml_data

Returns all data formatted as an XML string.

$obj->as_raw_data

Returns all data formatted as a Perl hash.

TODO

Support for more properties of a SOAP::Data object. Currently only type, uri, attributes, and value are supported.

A WSDL (and perhaps even an ASMX) parser may be included in the future to auto-generate ComplexType classes, thus eliminating nearly all the usual grunt effort of integrating a Perl application with complex applications running under modern SOAP services such as Apache Axis or Microsoft .NET.

Add a test suite.

Improve on this documentation.

CAVIATS

Changing the value of a field after it is set should also be able to support complex data structures, correctly imported into the complex type definition. Currently, only simple scalar values are supported.

The OBJ_FIELD data structure may change in future versions to more cleanly support SOAP::Data parameters. For now, I plan to keep it an array reference and simply append on new SOAP::Data parameters as they are implemented. Accessor methods may change as well, as the current interface is a little weak--it only returns first matched occurance of an element in the tree if there are multiple same-named elements.

BUGS

None known at this time. Bug reports and design suggestions are always welcome.

AUTHOR

Eric Rybski

COPYRIGHT AND LICENSE

Copyright 2005-2006 by Eric Rybski, All Rights Reserved

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

SOAP::Lite SOAP::Data::Builder