Phabricator::__call()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
ccs 0
cts 0
cp 0
rs 9.2
cc 2
eloc 15
nc 2
nop 2
crap 6
1
<?php namespace Phabricator;
2
3
use BuildR\Foundation\Exception\RuntimeException;
4
use Phabricator\ClientAwareTrait;
5
use Phabricator\Client\ClientInterface;
6
use Phabricator\Client\Curl\CurlClient;
7
use Phabricator\Exception\UnimplementedEndpointException;
8
use Phabricator\Request\RequestData;
9
use Phabricator\Response\ConduitResponse;
10
use ReflectionClass;
11
use ReflectionException;
12
13
/**
14
 * Phabricator PHP API main class that manage API class and result printing
15
 *
16
 * Phabricator PHP API
17
 *
18
 * @author Zoltán Borsos <[email protected]>
19
 * @package Phabricator
20
 *
21
 * @copyright    Copyright 2016, Zoltán Borsos.
22
 * @license      https://github.com/Zolli/Phabricator-PHP-API/blob/master/LICENSE.md
23
 * @link         https://github.com/Zolli/Phabricator-PHP-API
24
 *
25
 * @codingStandardIgnoreStart
26
 *
27
 * @method \Phabricator\Response\ConduitResponse User(string $methodName, array $methodData = []) Execute the method on User endpoint
28
 * @method \Phabricator\Response\ConduitResponse Token(string $methodName, array $methodData = []) Execute the method on Token endpoint
29
 * @method \Phabricator\Response\ConduitResponse Slowvote(string $methodName, array $methodData = []) Execute the method on Slowvote endpoint
30
 * @method \Phabricator\Response\ConduitResponse Repository(string $methodName, array $methodData = []) Execute the method on Repository endpoint
31
 * @method \Phabricator\Response\ConduitResponse Rremarkup(string $methodName, array $methodData = []) Execute the method on Remarkup endpoint
32
 * @method \Phabricator\Response\ConduitResponse Project(string $methodName, array $methodData = []) Execute the method on Project endpoint
33
 * @method \Phabricator\Response\ConduitResponse Phriction(string $methodName, array $methodData = []) Execute the method on Phriction endpoint
34
 * @method \Phabricator\Response\ConduitResponse Phrequest(string $methodName, array $methodData = []) Execute the method on Phrequest endpoint
35
 * @method \Phabricator\Response\ConduitResponse Phid(string $methodName, array $methodData = []) Execute the method on Phid endpoint
36
 * @method \Phabricator\Response\ConduitResponse Phame(string $methodName, array $methodData = []) Execute the method on Phame endpoint
37
 * @method \Phabricator\Response\ConduitResponse paste(string $methodName, array $methodData = []) Execute the method on paste endpoint
38
 * @method \Phabricator\Response\ConduitResponse Passphare(string $methodName, array $methodData = []) Execute the method on Passphare endpoint
39
 * @method \Phabricator\Response\ConduitResponse Owners(string $methodName, array $methodData = []) Execute the method on Owners endpoint
40
 * @method \Phabricator\Response\ConduitResponse Nuance(string $methodName, array $methodData = []) Execute the method on Nuance endpoint
41
 * @method \Phabricator\Response\ConduitResponse Maniphest(string $methodName, array $methodData = []) Execute the method on Maniphest endpoint
42
 * @method \Phabricator\Response\ConduitResponse Macro(string $methodName, array $methodData = []) Execute the method on Macro endpoint
43
 * @method \Phabricator\Response\ConduitResponse Harbormaster(string $methodName, array $methodData = []) Execute the method on Harbormaster endpoint
44
 * @method \Phabricator\Response\ConduitResponse Flag(string $methodName, array $methodData = []) Execute the method on Flag endpoint
45
 * @method \Phabricator\Response\ConduitResponse File(string $methodName, array $methodData = []) Execute the method on File endpoint
46
 * @method \Phabricator\Response\ConduitResponse Feed(string $methodName, array $methodData = []) Execute the method on Feed endpoint
47
 * @method \Phabricator\Response\ConduitResponse Diffusion(string $methodName, array $methodData = []) Execute the method on Diffusion endpoint
48
 * @method \Phabricator\Response\ConduitResponse Differential(string $methodName, array $methodData = []) Execute the method on Differential endpoint
49
 * @method \Phabricator\Response\ConduitResponse Conpherence(string $methodName, array $methodData = []) Execute the method on Conpherence endpoint
50
 * @method \Phabricator\Response\ConduitResponse Conduit(string $methodName, array $methodData = []) Execute the method on Conduit endpoint
51
 * @method \Phabricator\Response\ConduitResponse Chatlog(string $methodName, array $methodData = []) Execute the method on Chatlog endpoint
52
 * @method \Phabricator\Response\ConduitResponse Auth(string $methodName, array $methodData = []) Execute the method on Auth endpoint
53
 * @method \Phabricator\Response\ConduitResponse Audit(string $methodName, array $methodData = []) Execute the method on Audit endpoint
54
 *
55
 * @codingStandardIgnoreEnd
56
 */
57
class Phabricator {
58
59
    use ClientAwareTrait;
60
61
    /**
62
     * @type string Where is phabricator located
63
     */
64
    protected $phabricatorUrl;
65
66
    /**
67
     * @type string Contains the authenticated user token
68
     */
69
    protected $conduitToken;
70
71
    /**
72
     * @type array Cache the constructed endpoint ebjects
73
     */
74
    private $endpointObjectCache;
75
76
    /**
77
     * @type array Contains all unique endpoint handler
78
     */
79
    private $uniqueEndpointHandlers;
80
81
    /**
82
     * Phabricator constructor
83
     *
84
     * @param string $baseUrl
85
     * @param string $token
86
     * @param \Phabricator\Client\ClientInterface $client
87
     */
88 9
    public function __construct($baseUrl, $token, ClientInterface $client = NULL) {
89 9
        if($client === NULL) {
90 9
            $client = new CurlClient();
91 9
        }
92
93 9
        $this->setClient($client);
94
95 9
        $this->conduitToken = $token;
96 9
        $this->phabricatorUrl = $baseUrl;
97 9
    }
98
99
    /**
100
     * Pushes a unique handler to the stack. Unique handlers are preferred, over default handlers.
101
     * One endpoint only have on unique handler, and if you push another it will overwrite the previous
102
     *
103
     * @param string $apiName
104
     * @param string $handlerClassName The handler FQCN
105
     *
106
     * @throws \BuildR\Foundation\Exception\RuntimeException
107
     */
108 3
    public function pushEndpointHandler($apiName, $handlerClassName) {
109 3
        if(!class_exists($handlerClassName)) {
110 1
            throw new RuntimeException('This handler class (' . $handlerClassName . ') not found!');
111
        }
112
113 2
        $apiName = ucfirst(strtolower($apiName));
114
115 2
        $this->uniqueEndpointHandlers[$apiName] = $handlerClassName;
116
117
        //Invalidate the cache, if exist
118 2
        if(isset($this->endpointObjectCache[$apiName])) {
119 1
            unset($this->endpointObjectCache[$apiName]);
120 1
        }
121 2
    }
122
123
    /**
124
     * Proxy for undefined methods
125
     *
126
     * @param string $apiName The endpoint name
127
     * @param array $arguments arguments
128
     *
129
     * @throws \Phabricator\Exception\UnimplementedEndpointException
130
     *
131
     * @return \stdClass|NULL
132
     *
133
     * @codeCoverageIgnore
134
     */
135
    public function __call($apiName, $arguments) {
136
        $apiName = ucfirst(strtolower($apiName));
137
        $argData = $this->getDataByArguments($arguments);
138
139
        $methodName = $argData['methodName'];
140
        $requestData = (new RequestData($argData['methodArgs'], $this->conduitToken))->getResult();
141
        $requestUrl = $this->phabricatorUrl . '/api/' . strtolower($apiName) . "." . strtolower($methodName);
142
        $neededClass = $this->getHandlerClassName($apiName);
143
144
        try {
145
            $endpointReflector = new ReflectionClass($neededClass);
146
        } catch(ReflectionException $e) {
147
            throw new UnimplementedEndpointException("This API endpoint: {$apiName} is not implemented yet!");
148
        }
149
150
        $methodReflector = $this->getExecutorMethod($methodName, $endpointReflector);
151
        $endpointInstance = $this->getEndpointHandler($apiName, $endpointReflector);
152
153
        //Returning the response from request
154
        $result = $methodReflector->invokeArgs($endpointInstance, [$requestUrl, $requestData]);
155
        return new ConduitResponse($result);
156
    }
157
158
    /**
159
     * Returns a new instance from the given endpoint handler. In the instance creation the client is
160
     * passed to tha handler as parameter.
161
     *
162
     * This method also do runtime caching. All endpoint handler cached by name
163
     *
164
     * @param string $apiName Used as cache key name
165
     * @param \ReflectionClass $endpointReflector
166
     *
167
     * @return \Phabricator\Endpoints\EndpointInterface
168
     */
169 2
    protected function getEndpointHandler($apiName, ReflectionClass $endpointReflector) {
170 2
        if(isset($this->endpointObjectCache[$apiName])) {
171 1
            return $this->endpointObjectCache[$apiName];
172
        }
173
174
        //Create a new instance and store it
175 1
        $endpointInstance = $endpointReflector->newInstanceArgs([$this->getClient()]);
176 1
        $this->endpointObjectCache[$apiName] = $endpointInstance;
177
178 1
        return $endpointInstance;
179
    }
180
181
    /**
182
     * Return the reflector of the method that can execute the query on the
183
     * endpoint.
184
     *
185
     * @param string $methodName Like "query"
186
     * @param \ReflectionClass $endpointReflector
187
     *
188
     * @return \ReflectionMethod
189
     */
190 1
    protected function getExecutorMethod($methodName, ReflectionClass $endpointReflector) {
191 1
        $neededMethod = strtolower($methodName) . "Executor";
192
193 1
        if(!$endpointReflector->hasMethod($neededMethod)) {
194 1
            $neededMethod = "defaultExecutor";
195 1
        }
196
197 1
        return $endpointReflector->getMethod($neededMethod);
198
    }
199
200
    /**
201
     * Returns the FQCN of the handler class. Returns the default handler if no
202
     * unique handler available for the given endpoint.
203
     *
204
     * @param string $apiName Like "Project"
205
     *
206
     * @return string
207
     */
208 1
    protected function getHandlerClassName($apiName) {
209 1
        $apiName = ucfirst(strtolower($apiName));
210 1
        $neededClass = __NAMESPACE__ . '\\' . 'Endpoints\\Defaults\\' . $apiName;
211
212 1
        if(isset($this->uniqueEndpointHandlers[$apiName])) {
213 1
            $neededClass = $this->uniqueEndpointHandlers[$apiName];
214 1
        }
215
216 1
        return $neededClass;
217
    }
218
219
    /**
220
     * Get the base date by the passed array. The returned array contains the method name (on endpoint)
221
     * and the arguments that the called method can give.
222
     *
223
     * Returned array keys:
224
     * - (string) methodName
225
     * - (array) methodArgs
226
     *
227
     * @param array $arguments The magic method argument array
228
     *
229
     * @throws \BuildR\Foundation\Exception\RuntimeException
230
     *
231
     * @return array
232
     */
233 2
    protected function getDataByArguments(array $arguments) {
234 2
        if(!isset($arguments[0])) {
235 1
            throw new RuntimeException('The arguments not contains the method name!');
236
        }
237
238 1
        $methodName = (string) $arguments[0];
239 1
        $methodArgs = [];
240
241 1
        if(isset($arguments[1]) && is_array($arguments[1])) {
242 1
            $methodArgs = $arguments[1];
243 1
        }
244
245
        return [
246 1
            'methodName' => $methodName,
247 1
            'methodArgs' => $methodArgs,
248 1
        ];
249
    }
250
251
} 
252