Completed
Push — master ( e6cd79...15515b )
by Mike
02:12
created

AbstractEntryPoint::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.2
cc 4
eloc 10
nc 4
nop 2
1
<?php
2
3
namespace SugarAPI\SDK\EntryPoint\Abstracts;
4
5
6
use SugarAPI\SDK\EntryPoint\Interfaces\EPInterface;
7
use SugarAPI\SDK\Exception\EntryPointException;
8
use SugarAPI\SDK\Request\POST;
9
use SugarAPI\SDK\Response\JSON as JSONResponse;
10
11
abstract class AbstractEntryPoint implements EPInterface {
12
13
    protected $_AUTH_REQUIRED = true;
14
    protected $_MODULE;
15
    protected $_URL;
16
    protected $_REQUIRED_DATA;
17
18
    protected $url;
19
    protected $Module;
20
    protected $Options = array();
21
    protected $Data;
22
    protected $Request;
23
    protected $Response;
24
25
    public function __construct($url,$options = array()){
26
        $this->url = $url;
27
        $this->Module = $this->_MODULE;
28
29
        if (!empty($options)) {
30
            if (empty($this->Module)) {
31
                if (strpos($this->_URL, '$module') !== FALSE) {
32
                    $this->module($options[0]);
33
                    array_shift($options);
34
                }
35
            }
36
            $this->options($options);
37
        }
38
        $this->setupRequest();
39
    }
40
41
    /**
42
     * @inheritdoc
43
     */
44
    public function module($module){
45
        $this->Module = $module;
46
        return $this;
47
    }
48
49
    /**
50
     * @inheritdoc
51
     */
52
    public function options(array $options){
53
        $this->Options = $options;
54
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (SugarAPI\SDK\EntryPoint\...acts\AbstractEntryPoint) is incompatible with the return type declared by the interface SugarAPI\SDK\EntryPoint\...es\EPInterface::options of type SugarAPI\SDK\EntryPoint\...SugarAPI\SDK\EntryPoint.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
55
    }
56
57
    /**
58
     * @inheritdoc
59
     */
60
    public function data(array $data){
61
        $this->Data = $data;
62
        $this->Request->setBody($this->Data);
63
        return $this;
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function execute(){
70
        if ($this->verifyURL() && $this->validateData()) {
71
            $this->configureURL();
72
            $this->Request->setURL($this->url);
73
            $this->Request->send();
74
            $this->setupResponse();
75
            //Trying to manage memory by closing Curl Resource
76
            $this->Request->close();
77
        }
78
        return $this;
79
    }
80
81
82
    /**
83
     * @inheritdoc
84
     */
85
    public function authRequired() {
86
        return $this->_AUTH_REQUIRED;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92
    public function getModule() {
93
        return $this->Module;
94
    }
95
96
    /**
97
     *
98
     */
99
    public function getData(){
100
        return $this->Data;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->Data; (array) is incompatible with the return type declared by the interface SugarAPI\SDK\EntryPoint\...es\EPInterface::getData of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106
    public function getURL(){
107
        return $this->url;
108
    }
109
110
    /**
111
     * @inheritdoc
112
     */
113
    public function getResponse(){
114
        return $this->Response;
115
    }
116
117
    /**
118
     * @inheritdoc
119
     */
120
    public function getRequest(){
121
        return $this->Request;
122
    }
123
124
    /**
125
     * Configures the URL, by updating any variable placeholders in the URL property on the EntryPoint
126
     * - Replaces $module with $this->Module
127
     * - Replcaes all other variables starting with $, with options in the order they were given
128
     */
129
    protected function configureURL(){
130
        $url = $this->_URL;
131
        if (strpos($this->_URL,"$")!==FALSE) {
132
            if (count($this->Options) > 0 || !empty($this->Module)) {
133
                $urlParts = explode("/", $this->_URL);
134
                $o = 0;
135
                foreach ($urlParts as $key => $part) {
136
                    if (strpos($part, '$module') !== FALSE) {
137
                        if (isset($this->Module)) {
138
                            $urlParts[$key] = $this->Module;
139
                            continue;
140
                        } else {
141
                            if (isset($this->Options[$o])) {
142
                                $this->Module = $this->Options[$o];
143
                                array_shift($this->Options);
144
                            }
145
                        }
146
                    }
147
                    if (strpos($part, "$") !== FALSE) {
148
                        if (isset($this->Options[$o])) {
149
                            $urlParts[$key] = $this->Options[$o];
150
                            $o++;
151
                        }
152
                    }
153
                }
154
                $url = implode($urlParts,"/");
155
            }
156
        }
157
        $this->url = $this->url.$url;
158
    }
159
160
    /**
161
     * Setup the Request Object property, setup on initial Construct of EntryPoint
162
     */
163
    protected function setupRequest(){
164
        $this->Request = new POST();
165
    }
166
167
    /**
168
     * Setup the Response Object Property, not called until after Request Execution
169
     */
170
    protected function setupResponse(){
171
        $this->Response = new JSONResponse($this->Request->getResponse(),$this->Request->getCurlObject());
172
    }
173
174
    /**
175
     * Verify URL variables have been removed, and that valid number of options were passed.
176
     * @return bool
177
     * @throws EntryPointExecutionFailure
178
     */
179
    protected function verifyURL(){
180
        $urlVarCount = substr_count($this->_URL,"$");
181
        $optionCount = 0;
182
        if (!empty($this->Module)){
183
            $optionCount++;
184
        }
185
        $optionCount += count($this->Options);
186
        if ($urlVarCount!==$optionCount){
187
            if (empty($this->Module) && strpos($this->_URL,'$module')){
188
                throw new EntryPointException('Module is required for EntryPoint '.get_called_class());
189
            }else{
190
                throw new EntryPointException('EntryPoint URL ('.$this->_URL.') requires more parameters than passed.');
191
            }
192
        }else{
193
            return true;
194
        }
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    protected function validateData(){
201
        if (empty($this->_REQUIRED_DATA)||count($this->_REQUIRED_DATA)==0){
202
            return true;
203
        }else{
204
            $errors = array();
205
            foreach($this->_REQUIRED_DATA as $property){
206
                if (isset($this->Data[$property]) || $this->Data[$property]!==null){
207
                    continue;
208
                }else{
209
                    $errors[] = $property;
210
                }
211
            }
212
            if (count($errors)>0){
213
                throw new EntryPointException('EntryPoint requires specific properties in Request data. Missing the following '.implode($errors,","));
214
            }else{
215
                return true;
216
            }
217
        }
218
    }
219
220
}