A Component to help creating SOAP services

By Charles Gunawan (char101)
A component providing automatic WSDL generation using jool.nl Webservice Helper library, CakePHP caching of generated WSDL, and automatic handling of SOAP calls.

Concepts


  • SOAP methods will be implemented as model class methods. Models that will handle SOAP calls will have suffix Service, for example BookService
  • The Soap component will provide function to generate WSDL from the model class definition and to handle SOAP calls to the model class methods.
  • A controller will act as a SOAP proxy to the service models using the SOAP component


Requirements


  • PHP Soap extension
  • CakePHP 1.2 (haven't tested it in CakePHP 1.1)


Webservice Handler library


Download Webservice Handler library from jool.nl site and extract the contents into app/vendors/wshelper.

Create the SOAP component


Save the code below into app/controllers/components/soap.php.
The generated WSDL will be cached into app/tmp/cache directory. If DEBUG configuration
is greater than 0, the cache file modified time will be compared the model file
modified time and updated appropriately.

Component Class:

Download code <?php 
vendor
('wshelper/lib/soap/IPReflectionClass.class');
vendor('wshelper/lib/soap/IPReflectionCommentParser.class');
vendor('wshelper/lib/soap/IPXMLSchema.class');
vendor('wshelper/lib/soap/IPReflectionMethod.class');
vendor('wshelper/lib/soap/WSDLStruct.class');
vendor('wshelper/lib/soap/WSDLException.class');

/**
 * Class SoapComponent
 *
 * Generate WSDL and handle SOAP calls
 */
class SoapComponent extends Component
{
    var 
$params = array();

    function 
initialize(&$controller)
    {
        
$this->params $controller->params;
    }
    
    
/**
     * Get WSDL for specified model.
     *
     * @param string $modelClass : model name in camel case
     * @param string $serviceMethod : method of the controller that will handle SOAP calls
     */
    
function getWSDL($modelId$serviceMethod 'call')
    {
        
$modelClass $this->__getModelClass($modelId);
        
$expireTime '+1 year';
        
$cachePath $modelClass '.wsdl';
        
        
// Check cache if exist
        
$wsdl cache($cachePathnull$expireTime);

        
// If DEBUG > 0, compare cache modified time to model file modified time
        
if ((Configure::read() > 0) && (! is_null($wsdl))) {

            
$cacheFile CACHE $cachePath;
            if (
is_file($cacheFile)) {
                
$modelMtime filemtime($this->__getModelFile($modelId));
                
$cacheMtime filemtime(CACHE $cachePath);
                if (
$modelMtime $cacheMtime) {
                    
$wsdl null;
                }
            }

        }
        
        
// Generate WSDL if not cached
        
if (is_null($wsdl)) {
        
            
$refl = new IPReflectionClass($modelClass);
            
            
$controllerName $this->params['controller'];
            
$serviceURL Router::url("/$controllerName/$serviceMethod"true);

            
$wsdlStruct = new WSDLStruct('http://schema.example.com'
                                         
$serviceURL '/' $modelId
                                         
SOAP_RPC
                                         
SOAP_LITERAL);
            
$wsdlStruct->setService($refl);
            try {
                
$wsdl $wsdlStruct->generateDocument();
                
// cache($cachePath, $wsdl, $expireTime);
            
} catch (WSDLException $exception) {
                if (
Configure::read() > 0) {
                    
$exception->Display();
                    exit();
                } else {
                    return 
null;
                }
            }
        }

        return 
$wsdl;
    }

    
/**
     * Handle SOAP service call
     *
     * @param string $modelId : underscore notation of the called model
     *                          without _service ending
     * @param string $wsdlMethod : method of the controller that will generate the WSDL
     */
    
function handle($modelId$wsdlMethod 'wsdl')
    {
        
$modelClass $this->__getModelClass($modelId);
        
$wsdlCacheFile CACHE $modelClass '.wsdl';

        
// Try to create cache file if not exists
        
if (! is_file($wsdlCacheFile)) {
            
$this->getWSDL($modelId);
        }

        if (
is_file($wsdlCacheFile)) {
            
$server = new SoapServer($wsdlCacheFile);
        } else {
            
$controllerName $this->params['controller'];
            
$wsdlURL Router::url("/$controllerName/$wsdlMethod"true);
            
$server = new SoapServer($wsdlURL '/' $modelId);
        }
        
$server->setClass($modelClass);
        
$server->handle();
    }

    
/**
     * Get model class for specified model id
     *
     * @access private
     * @return string : the model id
     */
    
function __getModelClass($modelId)
    {
        
$inflector = new Inflector;
        return (
$inflector->camelize($modelId) . 'Service');
    }

    
/**
     * Get model id for specified model class
     *
     * @access private
     * @return string : the model id
     */
    
function __getModelId($modelClass)
    {
        
$inflector = new Inflector;
        return 
$inflector->underscore(substr($class0, -7));
    }

    
/**
     * Get model file for specified model id
     *
     * @access private
     * @return string : the filename
     */
    
function __getModelFile($modelId)
    {
        
$modelDir dirname(dirname(dirname(__FILE__))) . DS 'models';
        return 
$modelDir DS $modelId '_service.php';
    }
}
?>


Create the controller that will handle SOAP calls


This is an example controller. You can change the method name
that will handle SOAP calls and provide WSDL definition as you wish.
But don't forget to change the arguments to the handle and
getWSDL methods.
Save the file into app/controllers/service_controller.php

Controller Class:

Download code <?php 
class ServiceController extends AppController
{
    public 
$name 'Service';
    public 
$uses = array('TestService');
    public 
$helpers = array();
    public 
$components = array('Soap');

    
/**
     * Handle SOAP calls
     */
    
function call($model)
    {
        
$this->autoRender FALSE;
        
$this->Soap->handle($model'wsdl');
    }

    
/**
     * Provide WSDL for a model
     */
    
function wsdl($model)
    {
        
$this->autoRender FALSE;
        echo 
$this->Soap->getWSDL($model'call');
    }
}
?>


Create the service model


This is a test model. Save it into app/models/test_service.php

Model Class:

Download code <?php 
class TestService extends AppModel
{
    var 
$name 'TestService';
    var 
$useTable false;

    
/**
     * Divide two numbers
     *
     * @param float $a
     * @param float $b
     * @return float
     */
    
function divide($a$b)
    {
        if (
$b != 0) {
            return 
$a $b;
        }
        return 
0;
    }
}
?>


Testing the service


My favorite tool for testing SOAP services is SoapUI. You can use it or your
favorite tool to test the service. To access the WSDL, direct your tool to
http://yourhost/service/wsdl/test. The SOAPAction URL will be
http://yourhost/service/call/test.

 

Comments 549

CakePHP Team Comments Author Comments
 

Comment

1 Link to jool.nl webservice handler

Posted Oct 24, 2007 by Edmunds Kalnins
 

Comment

2 Undefined offset

Thanks for the article.

I am getting the following error after following this tutorial:


Notice (8): Undefined offset:  1 [CORE\app\vendors\wshelper\lib\soap\IPReflectionCommentParser.class.php, line 136]


I am using:
cake 1.2.0.5875 pre-beta
php 5.2.3
jool.nl webservices helper 1.5.0


It appears to throw an error when it attempts to generate the wsdl file for the first time from the ->setService($refl) call on line 64 of the soap.php component:


60            $wsdlStruct = new WSDLStruct('http://schema.example.com', 
61                                         $serviceURL . '/' . $modelId, 
62                                         SOAP_RPC, 
63                                         SOAP_LITERAL);
64            $wsdlStruct->setService($refl);
65            try {
66                $wsdl = $wsdlStruct->generateDocument();
67                // cache($cachePath, $wsdl, $expireTime);
68            } catch (WSDLException $exception) {


Any ideas?

NSM
Posted Nov 15, 2007 by Nathan
 

Comment

3 Has anyone had success with this tutorial

Just curious if anyone has implemented this tutorial successfully...
Posted Nov 16, 2007 by Nathan
 

Comment

4 The fix (maybe)

The model class inherits some functions from its parent class (cakePHP's base model class). The wshelper library requires that methods that do not return anything be tagged with @return void but since there are some of the inherited methods that do not have the @return tag, the library emits notice.

To fix it, simply edit the IPReflectionCommentParser.class.php around line 135, add the check

if (! isset($tagArr[1])) {
    $tagArr[1] = 'void';
}

just below the

case 'return':


Sorry if I didn't put this in the article.
Posted Nov 17, 2007 by Charles Gunawan
 

Comment

5 Maybe you can help

I make SOAP service, but with authorization(SOAP headers) and handler - controller and i have problems with it.
http://groups.google.com/group/cake-php/browse_thread/thread/1856af46a3589303#
Posted Mar 27, 2008 by wDevil