Completed
Pull Request — master (#7)
by Markus
06:25
created

SoapDispatcher::startup()   D

Complexity

Conditions 13
Paths 144

Size

Total Lines 120
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 60
nc 144
nop 0
dl 0
loc 120
rs 4.6605
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
263
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...