The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

SOAP::Data::ComplexType - An abstract class for creating and handling 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($request_obj->as_soap_data_instance( name => 'objInstance' ))
                        ->result;
        # An equivalent call...
        my $result2 = SOAP::Lite
                        ->uri($uri)
                        ->proxy($proxy)
                        ->somemethod(SOAP::Data->new(
                                        name => 'objInstance'
                                        type => &My::SOAP::Data::ComplexType::Bar::OBJ_TYPE,
                                        uri  => &My::SOAP::Data::ComplexType::Bar::OBJ_URI,
                                        attr => {}
                                )->value(\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 } }

Using arrays and Array type fields

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}, {key1 => val2}, {key1 => val3} ] }

In such cases, the field type definition must be 'ns:Array' (e.g. SOAP-ENC:Array) and you must define an 'arrayType' attribute (e.g. SOAP-ENC:arrayType); otherwise, it will be assumed that your field can only support scalar values. This is primarily due to functional requirements of how SOAP::Lite handles arrays, but also has implications on how ComplexType data values are returned by the various accessor methods. Specifically, if your object is a SOAP Array type, then accessor methods will return a list of elements in array context, or the number of elements in scalar context; however, if your object is any other type, then accessor methods will return all values in scalar context.

See SOAP::Data::ComplexType::Array for additional information and definition examples.

Object Methods

$obj->get( 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->get_elem( NAME )

Returns the object representing the request element. Rules for how the NAME parameter is used are the same as those defined for the "$obj-get( NAME )"> method.

$obj->set( 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_instance( name => INSTANCE_NAME )

Returns a SOAP::Data instance of the object, named INSTANCE_NAME.

$obj->as_soap_data

Returns all object fields as a list of SOAP::Data objects. Best for use with SOAP::Lite client stubs (subclasses), such as those generated by SOAP::Lite's stubmaker.pl.

$obj->as_xml_data_instance( name => INSTANCE_NAME )

Returns an instance of the object, named INSTANCE_NAME, formatted as an XML string.

$obj->as_xml_data

Returns all object fields as a list, formatted as an XML string.

$obj->as_raw_data

Returns all data formatted as a Perl hashref.

Other supported SOAP classes

SOAP::Data::ComplexType::Array

This is an abstract class that represents the native Array complex type in SOAP. See SOAP::Data::ComplexType::Array for more information and usage.

TODO

Finish rewriting internals to use a single complextype package instead of two, such that all methods can be used at any level of the hierarchy. This should simplify syntax needed for data mining lookup objects, and reduce complexity of manipulating the object overall.

Add a test suite to test expected list vs. scalar result sets for Array type complex objects and simple type objects.

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 support for restriction and range definitions (properties supported in a normal XML spec).

CAVIATS

Multi-dimensional and sparse arrays of simple type data are not yet supported, due to limitations of array data serialization capabilities in SOAP::Lite.

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. Simple 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

Bug reports and design suggestions are always welcome.

AUTHOR

Eric Rybski <rybskej@yahoo.com>.

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