Test Failed
Push — master ( d9b525...db8114 )
by Mike
02:47
created

AbstractEntryPoint   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 338
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 91.87%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 49
c 8
b 1
f 0
lcom 1
cbo 4
dl 0
loc 338
ccs 113
cts 123
cp 0.9187
rs 8.5454

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A setOptions() 0 5 1
A setData() 0 4 1
A setAuth() 0 4 1
A setUrl() 0 4 1
A setRequest() 0 4 1
A setResponse() 0 4 1
A getOptions() 0 3 1
A getData() 0 3 1
A getUrl() 0 3 1
A getResponse() 0 3 1
A getRequest() 0 3 1
A authRequired() 0 3 1
A configureRequest() 0 9 4
A configureAuth() 0 5 2
A configureData() 0 6 3
A configureDefaultData() 0 8 4
B configureURL() 0 18 5
A verifyUrl() 0 7 2
A execute() 0 11 3
A verifyData() 0 9 4
A verifyDataType() 0 6 2
A verifyRequiredData() 0 12 4
A requiresOptions() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractEntryPoint often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractEntryPoint, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SugarAPI\SDK\EntryPoint\Abstracts;
4
5
6
use SugarAPI\SDK\EntryPoint\Interfaces\EPInterface;
7
use SugarAPI\SDK\Exception\EntryPoint\InvalidRequestException;
8
use SugarAPI\SDK\Exception\EntryPoint\InvalidURLException;
9
use SugarAPI\SDK\Exception\EntryPoint\RequiredDataException;
10
use SugarAPI\SDK\Exception\EntryPoint\RequiredOptionsException;
11
use SugarAPI\SDK\Response\Interfaces\ResponseInterface;
12
use SugarAPI\SDK\Request\Interfaces\RequestInterface;
13
14
/**
15
 * Class AbstractEntryPoint
16
 * @package SugarAPI\SDK\EntryPoint\Abstracts
17
 * @method EPInterface ping()
18
 * @method EPInterface getRecord(string $module = '')
19
 * @method EPInterface getAttachment(string $module = '',string $record_id = '')
20
 * @method EPInterface getChangeLog(string $module = '',string $record_id = '')
21
 * @method EPInterface filterRelated(string $module = '')
22
 * @method EPInterface getRelated(string $module = '',string $record_id = '',string $relationship = '',string $related_id = '')
23
 * @method EPInterface me()
24
 * @method EPInterface search()
25
 * @method EPInterface oauth2Token()
26
 * @method EPInterface oauth2Refresh()
27
 * @method EPInterface createRecord()
28
 * @method EPInterface filterRecords()
29
 * @method EPInterface attachFile()
30
 * @method EPInterface oauth2Logout()
31
 * @method EPInterface createRelated()
32
 * @method EPInterface linkRecords()
33
 * @method EPInterface bulk()
34
 * @method EPInterface updateRecord()
35
 * @method EPInterface favorite()
36
 * @method EPInterface deleteRecord()
37
 * @method EPInterface unfavorite()
38
 * @method EPInterface deleteFile()
39
 * @method EPInterface unlinkRecords()
40
 */
41
abstract class AbstractEntryPoint implements EPInterface {
42
43
    /**
44
     * Whether or not Authentication is Required
45
     * @var bool
46
     */
47
    protected $_AUTH_REQUIRED = true;
48
49
    /**
50
     * The URL for the EntryPoint
51
     * - When configuring URL you define URL Parameters with $variables
52
     *      Examples:
53
     *          - Forecasts/$record_id
54
     * - $module Variable is a keyword to place the Module property into the URL
55
     *      Examples:
56
     *          - $module/$record
57
     * - Options property is used to replace variables in the order in which they are passed
58
     *
59
     * @var string
60
     */
61
    protected $_URL;
62
63
    /**
64
     * An array of Required Data properties that should be passed in the Request
65
     * @var array
66
     */
67
    protected $_REQUIRED_DATA = array();
68
69
    /**
70
     * The required type of Data to be given to the EntryPoint. If none, different types can be passed in.
71
     * @var string
72
     */
73
    protected $_DATA_TYPE;
74
75
    /**
76
     * The configured URL for the EntryPoint
77
     * @var string
78
     */
79
    protected $Url;
80
81
    /**
82
     * The initial URL passed into the EntryPoint
83
     * @var
84
     */
85
    protected $baseUrl;
86
87
    /**
88
     * The passed in Options for the EntryPoint
89
     * - If $module variable is used in $_URL static property, then 1st option will be used as Module
90
     * @var array
91
     */
92
    protected $Options = array();
93
94
    /**
95
     * The data being passed to the API EntryPoint
96
     * @var mixed - array||stdClass
97
     */
98
    protected $Data;
99
100
    /**
101
     * The Request Object, used by the EntryPoint to submit the data
102
     * @var RequestInterface
103
     */
104
    protected $Request;
105
106
    /**
107
     * The Response Object, returned by the Request Object
108
     * @var ResponseInterface
109
     */
110
    protected $Response;
111
112
    /**
113
     * Access Token for authentication
114
     * @var string
115
     */
116
    protected $accessToken;
117
118
119 1
    public function __construct($baseUrl,array $options = array()){
120 1
        $this->baseUrl = $baseUrl;
121
122 1
        if (!empty($options)) {
123 1
            $this->setOptions($options);
124 1
        }
125 1
        $this->configureURL();
126 1
    }
127
128
    /**
129
     * @inheritdoc
130
     */
131 1
    public function setOptions(array $options){
132 1
        $this->Options = $options;
133 1
        $this->configureURL();
134 1
        return $this;
135
    }
136
137
    /**
138
     * @inheritdoc
139
     * @throws RequiredDataException - When passed in data contains issues
140
     */
141 1
    public function setData($data){
142 1
        $this->Data = $data;
143 1
        return $this;
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149 1
    public function setAuth($accessToken) {
150 1
        $this->accessToken = $accessToken;
151 1
        return $this;
152
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157
    public function setUrl($url) {
158
        $this->Url = $url;
159
        return $this;
160
    }
161
162
    /**
163
     * @inheritdoc
164
     */
165 1
    public function setRequest(RequestInterface $Request) {
166 1
        $this->Request = $Request;
167 1
        return $this;
168
    }
169
170
    /**
171
     * @inheritdoc
172
     */
173
    public function setResponse(ResponseInterface $Response) {
174
        $this->Response = $Response;
175
        return $this;
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181 1
    public function getOptions(){
182 1
        return $this->Options;
183
    }
184
185
    /**
186
     * @inheritdoc
187
     */
188 1
    public function getData(){
189 1
        return $this->Data;
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 1
    public function getUrl(){
196 1
        return $this->Url;
197
    }
198
199
    /**
200
     * @inheritdoc
201
     */
202
    public function getResponse(){
203
        return $this->Response;
204
    }
205
206
    /**
207
     * @inheritdoc
208
     */
209 1
    public function getRequest(){
210 1
        return $this->Request;
211
    }
212
213
    /**
214
     * @inheritdoc
215
     * @param null $data - short form data for EntryPoint, which is configure by configureData method
216
     * @return $this
217
     * @throws InvalidRequestException
218
     * @throws InvalidURLException
219
     */
220 2
    public function execute($data = NULL){
221 2
        $data =  ($data === NULL?$this->Data:$data);
222 2
        $this->configureData($data);
223 2
        if (is_object($this->Request)) {
224 1
            $this->configureRequest();
225 1
            $this->Request->send();
226 1
        }else{
227 1
            throw new InvalidRequestException(get_called_class(),"Request property not configured");
228
        }
229 1
        return $this;
230
    }
231
232
    /**
233
     * @inheritdoc
234
     */
235 1
    public function authRequired() {
236 1
        return $this->_AUTH_REQUIRED;
237
    }
238
239
    /**
240
     * Verifies URL and Data are setup, then sets them on the Request Object
241
     * @throws InvalidURLException
242
     * @throws RequiredDataException
243
     */
244 1
    protected function configureRequest(){
245 1
        if ($this->verifyUrl()) {
246 1
            $this->Request->setURL($this->Url);
247 1
        }
248 1
        if ($this->verifyData() && !empty($this->Data)) {
249 1
            $this->Request->setBody($this->Data);
250 1
        }
251 1
        $this->configureAuth();
252 1
    }
253
254
    /**
255
     * Configures the authentication header on the Request object
256
     */
257 1
    protected function configureAuth(){
258 1
        if ($this->authRequired()) {
259 1
            $this->Request->addHeader('OAuth-Token', $this->accessToken);
260 1
        }
261 1
    }
262
263
    /**
264
     * Configures Data for the EntryPoint. Used mainly as an override function on implemented EntryPoints.
265
     * @var $data
266
     */
267 1
    protected function configureData($data){
268 1
        if (!empty($this->_REQUIRED_DATA)&&is_array($data)){
269 1
            $data = $this->configureDefaultData($data);
270 1
        }
271 1
        $this->setData($data);
272 1
    }
273
274
    /**
275
     * Configure Defaults on a Data array based on the Required Data property
276
     * @param array $data
277
     * @return array
278
     */
279 1
    protected function configureDefaultData(array $data){
280 1
        foreach($this->_REQUIRED_DATA as $property => $value){
281 1
            if (!isset($data[$property]) && $value!==NULL){
282 1
                $data[$property] = $value;
283 1
            }
284 1
        }
285 1
        return $data;
286
    }
287
288
    /**
289
     * Configures the URL, by updating any variable placeholders in the URL property on the EntryPoint
290
     * - Replaces $module with $this->Module
291
     * - Replaces all other variables starting with $, with options in the order they were given
292
     */
293 1
    protected function configureURL(){
294 1
        $url = $this->_URL;
295 1
        if ($this->requiresOptions()) {
296 1
            $urlParts = explode("/", $this->_URL);
297 1
            $o = 0;
298 1
            foreach ($urlParts as $key => $part) {
299 1
                if (strpos($part, "$") !== FALSE) {
300 1
                    if (isset($this->Options[$o])) {
301 1
                        $urlParts[$key] = $this->Options[$o];
302 1
                        $o++;
303 1
                    }
304 1
                }
305 1
            }
306 1
            $url = implode($urlParts,"/");
307 1
        }
308 1
        $url = $this->baseUrl.$url;
309 1
        $this->setUrl($url);
310 1
    }
311
312
    /**
313
     * Verify if URL is configured properly
314
     * @return bool
315
     * @throws InvalidURLException
316
     */
317 1
    protected function verifyUrl(){
318 1
        $UrlArray = explode("?",$this->Url);
319 1
        if (strpos($UrlArray[0],"$") !== FALSE){
320
            throw new InvalidURLException(get_called_class(),"Configured URL is ".$this->Url);
321
        }
322 1
        return true;
323
    }
324
325
    /**
326
     * Validate the Data property on the EntryPoint
327
     * @return bool
328
     * @throws RequiredDataException
329
     */
330 1
    protected function verifyData(){
331 1
        if (isset($this->_DATA_TYPE)||!empty($this->_DATA_TYPE)) {
332 1
            $this->verifyDataType();
333 1
        }
334 1
        if (!empty($this->_REQUIRED_DATA)){
335
            $this->verifyRequiredData();
336
        }
337 1
        return true;
338
    }
339
340
    /**
341
     * Validate DataType on the EntryPoint object
342
     * @return bool
343
     * @throws RequiredDataException
344
     */
345 1
    protected function verifyDataType(){
346 1
        if (gettype($this->Data) !== $this->_DATA_TYPE) {
347 1
            throw new RequiredDataException(get_called_class(),"Valid DataType is {$this->_DATA_TYPE}");
348 1
        }
349 1
        return true;
350 1
    }
351 1
352 1
    /**
353
     * Validate Required Data for the EntryPoint
354
     * @return bool
355
     * @throws RequiredDataException
356
     */
357
    protected function verifyRequiredData(){
358
        $errors = array();
359
        foreach($this->_REQUIRED_DATA as $property => $defaultValue){
360 2
            if (!isset($this->Data[$property])){
361 2
                $errors[] = $property;
362 1
            }
363
        }
364 1
        if (count($errors)>0){
365
            throw new RequiredDataException(get_called_class(),"Missing data for ".implode(",",$errors));
366
        }
367
        return true;
368
    }
369
370
    /**
371
     * Checks if EntryPoint URL contains requires Options
372 2
     * @return bool
373 2
     */
374 2
    protected function requiresOptions(){
375 2
        return strpos($this->_URL,"$") === FALSE?FALSE:TRUE;
376 1
    }
377
378
}