New article on PhpMaster.com about Zend_Pdf

I wrote a new article about using Zend_Pdf , which was published on PhpMaster.com days ago.

The PDF format is currently the most used format to exchange documents. If you provide your website users with printable versions of invoices, event tickets and other similar documents, you’ll most likely want to generate them as PDFs on the fly. In this article you will see how you can use Zend_Pdf to auto-generate PDF invoices.

I’m glad to write for the first time for PhpMaster, Sitepoint.com ‘s PHP blog. I have been reading their online content and books since 2005 and having the chance to write for them now is an honer.

check the article there.

Soap Web Services in PHP & hint on consuming from .Net

last week, I was working on implementing a SOAP server for one of our projects at work, this web service was built to be consumed by some other software that is built using .Net . It wasn’t that easy and I faced some problems that made me go crazy for some hours and I wanted to share them here with the solutions. let me first show you how I built the web service in PHP.

by the way, This project was built using CakePHP, but the web service I made doesn’t depend that much on that framework except that I use its MVC implementation.

If we want to build a SOAP web service, we will need first to create a WSDL file and make it accessible from a defined URL. I made a new controller with a new action named [index] that will return the WSDL file when requested using GET, and will also handle SOAP requests when requested using POST.

<?php

class SoapController extends AppController
{

    /**
     * handles SOAP requests
     */
    public function inedx()
    {

        if ( $this->RequestHandler->isGet() ) { //just show the wsdl
            $this->RequestHandler->respondAs('xml');
            echo self::_getWsdlContent();
            return;
        }

        $soapServer = new SoapServer( Configure::read('soap.wsdlFilePath') );

        $soapHandler = new Shreef_WebService_SoapHandler( /* Inject Dependencies here */ );

        $soapServer->setObject($soapHandler);

        $soapServer->handle();

    }

    /**
     * gets WSDL content for the SOAP service
     */
    protected static function _getWsdlContent()
    {
        $fileName = Configure::read('soap.wsdlFilePath');

        if (file_exists($fileName) ) {
            return file_get_contents($fileName);
        }

        $soapAutodiscover = new Zend_Soap_AutoDiscover( );

        $wsdlContent = $soapAutodiscover
                        ->setClass('Shreef_WebService_SoapHandler')
                        ->toXml();

        file_put_contents($fileName, $wsdlContent);
        return $wsdlContent;
    }

}

as you see, I’m dynamically generating the WSDL file using Zend_Soap_AutoDiscover. and caching it into a file. Zend_Soap_AutoDiscover will read the DocBlocks defined in Shreef_WebService_SoapHandler to build the WSDL file.

Notice that I gave SoapServer an instance of Shreef_WebService_SoapHandler instead of giving it the name of the class. this will give me the chance to inject any needed dependencies into the constructor.

here is my SoapHandler class with a simple method [getCurrentTime()] that will return a String, and another method [doSomething()] that returns Array.

<?php

class Shreef_WebService_SoapHandler
{

    /**
     * construct
     */
    public function __construct()
    {
        /** Dependency Injection happens here **/
    }

    /**
     * returns the current server time formated like YYYYMMDDhhmmss
     *
     * @return String
     */
    public function getCurrentTime()
    {
        return date('Ymdhis');
    }

    /**
     * do something
     *
     * @param String $something
     * @return Array
     */
    public function doSomething($something)
    {
        return array(1, 2, 3);
    }

    /** other methods **/
}

and that’s it, now if I open http://localhost/soap in my browser, I will see the WSDL. then I can use that WSDL to make requests to the web service. this was working fine on my machine and on the testing server, but it started to go crazy when we deployed that to the staging server. all methods returned null.

DocBlocks and OpCode caching

After some hours I noticed that the generated WSDL on the staging server was different than what was generated on my machine and on the testing server. there were no Out messages and the type of all method parameters was set to “anyType”. at first, I thought that this is a bug in Zend_Soap_AutoDiscover, but later I commented out the lines that cache the generated WSDL to a file and started to change things around.

I noticed that whenever I make a change to the file that contains the SoapHandler, the WSDL gets generated like expected for one time, but if I open the URL of the WSDL again, it will go back to generate the wrong WSDL. and here we have another “Aha!!” moment.

We have eAccelerator installed on that server. it is doing opcode caching for all the PHP code. this means that after every modification to the file that contains the SoapHandler class, the class is read from the file with all the DocBlocks, but in the next request, SoapHandler classs will get read from the opcode cache with all DockBlocks removed. so Zend_Soap_AutoDiscover won’t find DocBlocks to read and will guess that all methods are void and all method parameters are of type “anyType”.

As stopping eAccelerator doesn’t make sense, I turned on the WSDL file caching again and ran the “touch” command on all files that contain code that should get described in the WSDL ( the soap handler and any other complex types used ). this will make the WSDL get generated well the first time and cached.

I have to automate this later using a shell script or something.

.Net and Soap Arrays

The second problem was that Visual Studio.net was refusing to generate the required proxy classes using that WSDL file. It didn’t understand the type “Array”. I searched for this problem, and it looked like that were no way to make VS.net understand it. I solved that by instead of returning values of type Array, I changed my code a bit to return a complex type.

Also, I had to tell Zend_Soap_AutoDiscover that I want to use another WSDL strategy by passing the string ‘Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex’ to its constructor.

iconv misunderstands UTF-16 strings with no BOM

I had a problem last week with converting UTF-16 encoded strings to UTF-8 using PHP’s iconv library on a Linux server. my code worked fine on my machine but the same code resulted in a rubbish unreadable characters on our production server.

Let me take you to the beginning of the problem. I had a Hexadecimal representation of a UTF-16 string like that

0635 0628 0627 062D 0020 0627 0644 062E 064A 0631

this is the equivalent of “Good morning” in Arabic “صباح الخير” . I had some lines of code that will convert this into a normal stream of UTF-16 bytes so I can be able to use iconv to convert the string to UTF-8. maybe you noticed that there is no BOM at the beginning of Hexadecimal representation of the string. so let me quote what is written on Unicode’s BOM FAQ page about this.

Q: Why do some of the UTFs have a BE or LE in their label, such as UTF-16LE?

A: UTF-16 and UTF-32 use code units that are two and four bytes long respectively. For these UTFs, there are three sub-flavors: BE, LE and unmarked. The BE form uses big-endian byte serialization (most significant byte first), the LE form uses little-endian byte serialization (least significant byte first) and the unmarked form uses big-endian byte serialization by default, but may include a byte order mark at the beginning to indicate the actual byte serialization used.

so when there is no BOM, the string should be treated as big-endian. libiconv has a different opinion about this and will try to guess if it should use big-endian or little-endian depending on the operating system. so you will get different results on different machines.

The simple solution to this problem (after a long time trying to identify it), is to just tell iconv that I’m converting from UTF-16BE (big-endian) so it won’t try to guess the endianess of the bytes. so in php it will be like that

$result = iconv('UTF-16BE', 'UTF-8', $str);

or better, I can check the BOM before converting the Hexadecimal codes to a stream of bytes and taking the decision of converting from UTF-16BE or UTF-16LE depending on if it begins with FEFF or FFFE.