Attachments with php's built-in SoapClient?
Asked Answered
S

2

8

Is there a way I can add a soap attachment to a request using PHP's built-in SoapClient classes? It doesn't look like it's supported, but maybe I can manually build the mime boundaries? I know the PEAR SOAP library supports them, but in order to use that I have to rewrite my entire library to use it.

Skive answered 25/6, 2010 at 18:53 Comment(1)
I blogged about the full solution SoapClient with AttachmentsBreezy
D
5

Why don't you just send files using Data URI scheme rather than implement SoapAttachment ? Here is an example :

Client

$client = new SoapClient(null, array(
        'location' => "http://localhost/lab/stackoverflow/a.php?h=none",
        'uri' => "http://localhost/",
        'trace' => 1
));

// Method 1 Array
// File to upload
$file = "golf3.png";

// First Example
$data = array();
$data['name'] = $file;
$data['data'] = getDataURI($file, "image/png");
echo "Example 1: ";
echo ($return = $client->upload($data)) ? "File Uploaded : $return bytes" : "Error Uploading Files";

// Method 2 Objects
// File to upload
$file = "original.png";

// Second Example
$attachment = new ImageObj($file);
$param = new SoapVar($attachment, SOAP_ENC_OBJECT, "ImageObj");
$param = new SoapParam($param, "param");
echo "Example 2: ";
echo ($return = $client->uploadObj($attachment)) ? "File Uploaded : $return bytes" : "Error Uploading Files";

Output

Example 1: File Uploaded : 976182 bytes
Example 2: File Uploaded : 233821 bytes

Server

class UploadService {

    public function upload($args) {
        $file = __DIR__ . "/test/" . $args['name'];
        return file_put_contents($file, file_get_contents($args['data']));
    }

    public function uploadObj($args) {
        $file = __DIR__ . "/test/" . $args->name;
        $data = sprintf("data://%s;%s,%s", $args->mime, $args->encoding, $args->data);
        return file_put_contents($file, file_get_contents($data));
    }
}

try {
    $server = new SOAPServer(NULL, array(
            'uri' => 'http://localhost/'
    ));
    $server->setClass('UploadService');
    $server->handle();

} catch (SOAPFault $f) {
    print $f->faultstring;
}

Client Util

// Function Used
function getDataURI($image, $mime = '') {
    return 'data: ' . (function_exists('mime_content_type') ? 
            mime_content_type($image) : $mime) . ';base64,' . 
            base64_encode(file_get_contents($image));
}


// Simple Image Object
class ImageObj{
    function __construct($file, $mime = "") {
        $this->file = $file;
        $this->name = basename($file);
        if (function_exists('mime_content_type')) {
            $this->mime = mime_content_type($file);
        } elseif (function_exists('finfo_open')) {
            $this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file);
        } else {
            $this->mime = $mime;
        }

        $this->encoding = "base64";
        $this->data = base64_encode(file_get_contents($file));
    }
}
Durative answered 31/5, 2013 at 18:29 Comment(10)
That looks pretty sweet, and thanks for the example, but if I gather correctly, you're just using a simple type String to send the image. And since you have the server as well, you know how to parse out and decode the image. I don't think it will work in cases where I'm only writing the client and the service expects an attachment, or am I wrong about that?Breezy
@Breezy i think you should test it first ..... I used Data URI scheme and i testing it it works fineDurative
Interesting, looks like that's where that 'data: ' . (function_exists('mime_content_type') ? mime_content_type($image) : $mime) . ';base64,' bit came from, I was wondering about that. I'll take a deeper look at it.Breezy
Still experimenting, but it looks like the service I'm trying to hit to test it on is expecting SwA. Doesn't seem to be excepting requests with the paradigm you've shown.Breezy
While this isn't what I was looking for, it did drive me to do a lot of research and get a start implementing what I wanted. The bounty is yours!Breezy
Just saw your messages .... Are you implementing Server & Client or you are implementing a Soap Client that connects to a server ? And thanks for the bountyDurative
Implementing a client that talks to a foreign server right now. But once the attachment code is working, it could be used from SoapServer too. I already have the download side working, using php-mime-mail-parser. The upload piece has proven a bit harder (I think I have it, but the remote server doesn't LOL). I'm taking the approach I outlined in my answer for the implementation. NP on the bounty, thanks for your answer!Breezy
Can i see the wsdl .. you can put it on pastebin .. let me see what you are dealing with ... i may be able to helpDurative
um.voipconsultants.biz:8443/wsdl.fcgi?get=Voicemail.xsd get_vm_greeting, I've implemented, set_vm_greeting is what I'm working on.Breezy
That is one crappy web service .... i see why you are having issues .... SetVMGreetingRequest is a complex type which was not defined .. tell then to give you a sample wsdl request they are expecting ... i can have you figure how to compose the messageDurative
B
2

Yes, you can build the MIME component of the message using something like imap_mail_compose.

You'll need to construct a multipart message as they do in the first example, putting the XML from the $request parameter, from an overridden SoapClient::__doRequest method, into the first part of the MIME message.

Then you can do as others have shown in the first imap_mail_compose example to add one or more messages parts with attachments. These attachements can, but do not have to be base64 encoded, they can just as well be binary. The encoding for each part is specified by part-specific headers.

You'll also need to cook up an appropriate set of HTTP headers, per the SwA Document @Baba linked to earlier.

Once it's all said and done, you should have something looking like the examples from that document:

MIME-Version: 1.0
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml;
        start="<[email protected]>"
Content-Description: This is the optional message description.

--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <[email protected]>

<?xml version='1.0' ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
..
<theSignedForm href="cid:[email protected]"/>
..
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

--MIME_boundary
Content-Type: image/tiff
Content-Transfer-Encoding: binary
Content-ID: <[email protected]>

...binary TIFF image...
--MIME_boundary--

And you can send that across the wire with the aforementioned overridden SoapClient::__doRequest method. Things I have noticed in trying to implement it myself thus far:

  • You may need to create an href URI reference from each SOAP node to the corresponding attachment, something like href="cid:[email protected]" above
  • You will need to extract the boundary component from the MIME content returned by imap_mail_compose for use in an HTTP Content-Type header
  • Don't forget the start component of the Content-Type header either, it should look something like this:
  • imap_mail_compose appears fairly minimal (but low hanging fruit), if it proves insufficient, consider Mail_Mime instead

Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml; start=""

Lastly, I'm not sure how evenly the various implementations of SwA are out there on the Internet... Suffice it to say, I've not been able to get an upload to a remote service with a crude implementation of what I've described above yet. It does seem like SwA is the typical SOAP attachment paradigm of choice though, from what I gather reading around on the net.

Breezy answered 7/6, 2013 at 4:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.