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.
-
I blogged about the full solution [SoapClient with Attachments](http://quickshiftin.com/blog/2013/09/soap-client-attachments-php/) – quickshiftin Sep 02 '13 at 00:14
2 Answers
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));
}
}

- 94,024
- 28
- 166
- 217
-
That looks pretty sweet, and thanks for the example, but if I gather correctly, you're just using a [simple type String](http://docs.oracle.com/cd/E14154_01/dev.835/e12868/e01_soap_types.htm#sthref261) 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? – quickshiftin Jun 04 '13 at 05:20
-
@quickshiftin i think you should test it first ..... I used [Data URI scheme](http://en.wikipedia.org/wiki/Data_URI_scheme) and i testing it it works fine – Baba Jun 04 '13 at 06:56
-
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. – quickshiftin Jun 04 '13 at 07:06
-
Still experimenting, but it looks like the service I'm trying to hit to test it on is expecting [SwA](http://en.wikipedia.org/wiki/SOAP_with_Attachments). Doesn't seem to be excepting requests with the paradigm you've shown. – quickshiftin Jun 06 '13 at 06:54
-
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! – quickshiftin Jun 07 '13 at 05:53
-
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 bounty – Baba Jun 07 '13 at 06:13
-
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](https://code.google.com/p/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! – quickshiftin Jun 07 '13 at 06:48
-
Can i see the wsdl .. you can put it on pastebin .. let me see what you are dealing with ... i may be able to help – Baba Jun 07 '13 at 06:51
-
https://um.voipconsultants.biz:8443/wsdl.fcgi?get=Voicemail.xsd *get_vm_greeting*, I've implemented, *set_vm_greeting* is what I'm working on. – quickshiftin Jun 07 '13 at 06:59
-
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 message – Baba Jun 07 '13 at 07:16
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="<claim061400a.xml@claiming-it.com>"
Content-Description: This is the optional message description.
--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <claim061400a.xml@claiming-it.com>
<?xml version='1.0' ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
..
<theSignedForm href="cid:claim061400a.tiff@claiming-it.com"/>
..
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
--MIME_boundary
Content-Type: image/tiff
Content-Transfer-Encoding: binary
Content-ID: <claim061400a.tiff@claiming-it.com>
...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:claim061400a.tiff@claiming-it.com"
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.

- 66,362
- 10
- 68
- 89