SoapDispatcher::dispatch()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
// +---------------------------------------------------------------------------+
4
// | This file is part of the Agavi package.                                   |
5
// | Copyright (c) 2005-2011 the Agavi Project.                                |
6
// | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr.    |
7
// |                                                                           |
8
// | For the full copyright and license information, please view the LICENSE   |
9
// | file that was distributed with this source code. You can also view the    |
10
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
11
// |   vi: set noexpandtab:                                                    |
12
// |   Local Variables:                                                        |
13
// |   indent-tabs-mode: t                                                     |
14
// |   End:                                                                    |
15
// +---------------------------------------------------------------------------+
16
17
/**
18
 * AgaviSoapDispatcher handles SOAP requests.
19
 *
20
 * @package    agavi
21
 * @subpackage Dispatcher
22
 *
23
 * @author     David Zülke <[email protected]>
24
 * @copyright  Authors
25
 * @copyright  The Agavi Project
26
 *
27
 * @since      0.9.0
28
 *
29
 * @version    $Id$
30
 */
31
32
namespace Agavi\Dispatcher;
33
34
use Agavi\Config\ConfigCache;
35
use Agavi\Request\RequestDataHolder;
36
use Agavi\Response\Response;
37
38
class SoapDispatcher extends Dispatcher
39
{
40
41
    /** @var       RequestDataHolder Additional arguments for later use  */
42
    protected $dispatchArguments = null;
43
    
44
    /**
45
     * @var        ExecutionContainer Specific execution container to run.
46
     */
47
    protected $dispatchContainer = null;
48
    
49
    /**
50
     * @var        \SoapClient The soap client instance we use to access WSDL info.
51
     */
52
    protected $soapClient = null;
53
    
54
    /**
55
     * @var        \SoapServer The soap server instance that handles the request.
56
     */
57
    protected $soapServer = null;
58
    
59
    /**
60
     * Get the soap client instance we use to access WSDL info.
61
     *
62
     * @return     \SoapClient The soap client instance.
63
     *
64
     * @author     David Zülke <[email protected]>
65
     * @since      0.11.0
66
     */
67
    public function getSoapClient()
68
    {
69
        return $this->soapClient;
70
    }
71
    
72
    /**
73
     * Get the soap server instance we use to access WSDL info.
74
     *
75
     * @return     \SoapServer The soap client instance.
76
     *
77
     * @author     David Zülke <[email protected]>
78
     * @since      0.11.0
79
     */
80
    public function getSoapServer()
81
    {
82
        return $this->soapServer;
83
    }
84
    
85
    /**
86
     * Do any necessary startup work after initialization.
87
     *
88
     * This method is not called directly after initialize().
89
     *
90
     * @author     David Zülke <[email protected]>
91
     * @since      0.11.0
92
     */
93
    public function startup()
94
    {
95
        parent::startup();
96
        
97
        // user-supplied "wsdl" and "options" parameters
98
        $wsdl = $this->getParameter('wsdl');
99
        if (!$wsdl) {
100
            // no wsdl was specified, that means we generate one from the annotations in routing.xml
101
            $wsdl = $this->context->getRouting()->getWsdlPath();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Agavi\Routing\Routing as the method getWsdlPath() does only exist in the following sub-classes of Agavi\Routing\Routing: Agavi\Routing\SoapRouting. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
102
        }
103
        $this->setParameter('wsdl', $wsdl);
104
        
105
        // get the name of the class to use for the client, defaults to PHP's own "SoapClient"
106
        $soapClientClass = $this->getParameter('soap_client_class', 'SoapClient');
107
        $soapClientOptions = $this->getParameter('soap_client_options', array());
108
        // get the name of the class to use for the server, defaults to PHP's own "SoapServer"
109
        $soapServerClass = $this->getParameter('soap_server_class', 'SoapServer');
110
        $soapServerOptions = $this->getParameter('soap_server_options', array());
111
        // get the name of the class to use for handling soap calls, defaults to Agavi's "AgaviSoapDispatcherCallHandler"
112
        $soapHandlerClass = $this->getParameter('soap_handler_class', 'AgaviSoapDispatcherCallHandler');
113
        
114
        // force client's soap version to be the same as the server's
115
        if (isset($soapServerOptions['soap_version'])) {
116
            $soapClientOptions['soap_version'] = $soapServerOptions['soap_version'];
117
        }
118
        
119
        // force client's cache_wsdl setting to be the same as the server's
120
        if (isset($soapServerOptions['cache_wsdl'])) {
121
            // and cast it to an int
122
            $soapServerOptions['cache_wsdl'] = (int)$soapServerOptions['cache_wsdl'];
123
            $soapClientOptions['cache_wsdl'] = $soapServerOptions['cache_wsdl'];
124
        }
125
        
126
        if (isset($soapServerOptions['features'])) {
127
            // cast this to an int
128
            $soapServerOptions['features'] = (int)$soapServerOptions['features'];
129
        }
130
        
131
        // create a client, so we can grab the functions and types defined in the wsdl (not possible from the server, duh)
132
        $this->soapClient = new $soapClientClass($wsdl, $soapClientOptions);
133
        
134
        if ($this->getParameter('auto_classmap')) {
135
            // we have to create a classmap automatically.
136
            // to do that, we read the defined types, and set identical values for type and class name.
137
            $classmap = array();
138
            
139
            // with an optional prefix, of course.
140
            $prefix = $this->getParameter('auto_classmap_prefix', '');
141
            
142
            foreach ($this->soapClient->__getTypes() as $definition) {
143
                if (preg_match('/^struct (\S+) \{$/m', $definition, $matches)) {
144
                    $classmap[$matches[1]] = $prefix . $matches[1];
145
                }
146
            }
147
            
148
            if (isset($soapServerOptions['classmap'])) {
149
                $classmap = array_merge((array) $classmap, $soapServerOptions['classmap']);
150
            }
151
            
152
            $soapServerOptions['classmap'] = $classmap;
153
        }
154
        
155
        // create a server
156
        $this->soapServer = new $soapServerClass($wsdl, $soapServerOptions);
157
        
158
        $newSoapHandlerClass = $soapHandlerClass . 'WithAutoHeaders';
159
        
160
        // build the special extension class to the handler that contains methods for each of the headers
161
        if ($this->getParameter('auto_headers', true)) {
162
            // the cache filename we'll be using
163
            $cache = ConfigCache::getCacheName($soapHandlerClass, $this->context->getName());
164
            
165
            if (ConfigCache::isModified($wsdl, $cache)) {
166
                $doc = new \DOMDocument();
167
                $doc->load($wsdl);
168
                $xpath = new \DOMXPath($doc);
169
                $xpath->registerNamespace('soap', 'http://schemas.xmlsoap.org/wsdl/soap/');
170
                
171
                $code = array();
172
                
173
                $code[] = '<?php';
174
                $code[] = sprintf('class %s extends %s {', $newSoapHandlerClass, $soapHandlerClass);
175
                $code[] = '  protected $rd;';
176
                $code[] = '  public function __construct(Context $context) {';
177
                $code[] = '    parent::__construct($context);';
178
                $code[] = '    $this->rd = $this->context->getRequest()->getRequestData();';
179
                $code[] = '  }';
180
                
181
                $headers = array();
182
183
                /** @var \DOMElement $header */
184
                foreach ($xpath->query('//soap:header') as $header) {
185
                    $name = $header->getAttribute('part');
186
                    
187
                    if (in_array($name, $headers)) {
188
                        continue;
189
                    }
190
                    $headers[] = $name;
191
                    
192
                    $code[] = sprintf('  public function %s($value) {', $name);
193
                    $code[] = sprintf('    $this->rd->setHeader(%s, $value);', var_export($name, true));
194
                    $code[] = '  }';
195
                }
196
                
197
                $code[] = '}';
198
                $code[] = '?>';
199
                
200
                $code = implode("\n", $code);
201
                
202
                ConfigCache::writeCacheFile($soapHandlerClass, $cache, $code);
203
            }
204
            
205
            include($cache);
206
        }
207
        
208
        // give it a class that handles method calls
209
        // that class uses __call
210
        // the class ctor gets the context as the first argument
211
        $this->soapServer->setClass($newSoapHandlerClass, $this->context);
212
    }
213
    /**
214
     * Dispatch a request
215
     *
216
     * @param      RequestDataHolder  $arguments An optional request data holder object
217
     *                                     with additional request data.
218
     * @param      ExecutionContainer $container An optional execution container that,
219
     *                                     if given, will be executed right away,
220
     *                                     skipping routing execution.
221
     *
222
     * @return     Response The response produced during this dispatch call.
223
     *
224
     * @author     David Zülke <[email protected]>
225
     * @since      0.11.0
226
     */
227
    public function dispatch(RequestDataHolder $arguments = null, ExecutionContainer $container = null)
228
    {
229
        // Remember The Milk... err... the arguments given.
230
        $this->dispatchArguments = $arguments;
231
        // and the container, too, if there was one
232
        $this->dispatchContainer = $container;
233
        
234
        // handle the request. the aforementioned __call will be run next
235
        // we use the input from the request as the argument, it contains the SOAP request
236
        // no need to send the response as SoapServer does that
237
        $this->soapServer->handle($this->context->getRequest()->getInput());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Agavi\Request\Request as the method getInput() does only exist in the following sub-classes of Agavi\Request\Request: Agavi\Request\ConsoleRequest, Agavi\Request\SoapRequest, Agavi\Request\WebserviceRequest, Agavi\Request\XmlrpcepiphpRequest. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
238
    }
239
    
240
    /**
241
     * A method that is called in the __call overload by the SOAP call handler.
242
     *
243
     * All it does is call parent::dispatch() to prevent an infinite loop.
244
     *
245
     * @author     David Zülke <[email protected]>
246
     * @since      0.11.0
247
     */
248
    public function doDispatch()
249
    {
250
        try {
251
            // return the content so SoapServer can send it.
252
            // SoapResponse::send() does not send the content, but sets the headers on the SoapServer
253
            return parent::dispatch($this->dispatchArguments, $this->dispatchContainer);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (dispatch() instead of doDispatch()). Are you sure this is correct? If so, you might want to change this to $this->dispatch().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
254
        } catch (\SoapFault $f) {
255
            $this->response->clear();
256
            $this->response->setContent($f);
257
            // return the content so SoapServer can send it.
258
            return $this->response;
259
        }
260
    }
261
}
262