BaseRequest   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 890
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 60
eloc 143
dl 0
loc 890
rs 3.6
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getChildInstance() 0 7 2
A __construct() 0 5 1
A setMode() 0 4 1
A enableMode() 0 3 1
A setEndpoint() 0 3 1
A getQueryParameters() 0 19 4
A processResponse() 0 14 2
A getMethod() 0 3 1
A enablePagination() 0 3 1
A getBody() 0 3 1
A paginate() 0 4 1
A setMethod() 0 3 1
A enableTimes() 0 2 1
A addQueryParameter() 0 3 1
A call() 0 37 4
A minusPage() 0 5 1
A getDefaultGuzzleOptions() 0 19 4
A addPage() 0 5 1
A setContentType() 0 5 3
A previous() 0 8 3
A createClient() 0 13 1
A next() 0 9 3
A setPage() 0 9 2
A setAPIParameters() 0 7 3
A getContentType() 0 8 3
A setResponseClass() 0 3 1
A getFullURL() 0 8 2
A setBody() 0 3 1
A getEndPoint() 0 3 1
A getAllPages() 0 12 2
A getResponse() 0 7 2
A getReturnDetails() 0 12 3
A get() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BaseRequest 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.

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 BaseRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * BaseRequest Class, provides ability to create API calls
4
 */
5
namespace Twigger\UnionCloud\API\Request;
6
7
use GuzzleHttp\Client;
8
use GuzzleHttp\HandlerStack;
9
use GuzzleHttp\Middleware;
10
use GuzzleHttp\Psr7\Request;
11
use GuzzleHttp\Psr7\Response;
12
use Twigger\UnionCloud\API\Auth\Authentication;
13
use Twigger\UnionCloud\API\Configuration;
14
use Twigger\UnionCloud\API\Exception\Pagination\PageNotFoundException;
15
use Twigger\UnionCloud\API\Exception\Request\IncorrectRequestParameterException;
16
use Twigger\UnionCloud\API\Exception\Request\RequestHistoryNotFound;
17
use Twigger\UnionCloud\API\Exception\Response\BaseResponseException;
18
use Twigger\UnionCloud\API\Exception\Response\ResponseMustInheritIResponse;
19
use Twigger\UnionCloud\API\ResourceCollection;
20
use Twigger\UnionCloud\API\Response\BaseResponse;
21
use Twigger\UnionCloud\API\Response\IResponse;
22
23
/**
24
 * Contains helper functions relevant to making a request
25
 *
26
 * @package Twigger\UnionCloud\API\Core\Requests
27
 * @license    https://opensource.org/licenses/GPL-3.0  GNU Public License v3
28
 * @author     Toby Twigger <[email protected]>
29
 */
30
31
class BaseRequest
32
{
33
34
    /**
35
     * Authentication wrapper
36
     *
37
     * @var Authentication
38
     */
39
    protected $authentication;
40
41
    /**
42
     * Configuration wrapper
43
     *
44
     * @var Configuration
45
     */
46
    protected $configuration;
47
48
    /**
49
     * A response class that implements IResponse
50
     *
51
     * @var BaseResponse
52
     */
53
    private $responseClass;
54
55
    /*
56
   |--------------------------------------------------------------------------
57
   | API parameters
58
   |--------------------------------------------------------------------------
59
   |
60
   | API Parameters to directly make up the API endpoint request
61
   |
62
   */
63
64
    /**
65
     * Body of the request
66
     *
67
     * @var array
68
     */
69
    private $body;
70
71
    /**
72
     * Method to use for the API request
73
     *
74
     * POST, GET etc.
75
     *
76
     * @var string
77
     */
78
    private $method;
79
80
    /**
81
     * Content Type of the request
82
     *
83
     * This is either
84
     *      -application\json if the body is a string
85
     *      -x-www-form-encoded if the body is an array
86
     * @var string
87
     */
88
    private $contentType;
89
90
    /**
91
     * Endpoint to send the API request to
92
     *
93
     * Relevant to '/api/'
94
     *
95
     * @var string
96
     */
97
    private $endPoint;
98
99
    /**
100
     * Query parameters to enter at the end of the URL
101
     *
102
     * @var array
103
     */
104
    private $queryParameters = [];
105
106
    /**
107
     * If this is true, the 'mode' query parameter will be set
108
     *
109
     * @var bool
110
     */
111
    private $useMode = false;
112
113
    /**
114
     * Mode to use for the request.
115
     *
116
     * @var string $mode basic|standard|full
117
     */
118
    private $mode = 'full';
119
120
    /**
121
     * Should pagination be used?
122
     *
123
     * This will be set by the Request Class, i,e, by the developer
124
     *
125
     * @var bool $paginates
126
     */
127
    private $paginates = false;
128
129
    /**
130
     * Page to use.
131
     *
132
     * This will be put into the query parameters if $pagination is true
133
     *
134
     * @var int $page
135
     */
136
    private $page = 1;
137
138
139
140
141
142
143
144
145
146
147
148
149
150
    /*
151
   |--------------------------------------------------------------------------
152
   | Parameters to pass to Response
153
   |--------------------------------------------------------------------------
154
   |
155
   | These parameters are set so they can be passed to
156
   | the response class
157
   |
158
   */
159
160
    /**
161
     * Response captured from Guzzle for debugging
162
     *
163
     * @var Response
164
     */
165
    private $debugResponse;
166
167
    /**
168
     * Request captured from Guzzle for debugging
169
     * @var Request
170
     */
171
    private $debugRequest;
172
173
    /**
174
     * Request options passed into Guzzle, for debugging
175
     * @var array
176
     */
177
    private $requestOptions;
178
179
    /**
180
     * Container for the Guzzle HTTP Stack
181
     *
182
     * @var array
183
     */
184
    private $container = [];
185
186
187
188
189
190
191
192
193
194
195
    /*
196
    |--------------------------------------------------------------------------
197
    | Response Parameters
198
    |--------------------------------------------------------------------------
199
    |
200
    | Parameters to control the response information
201
    |
202
    */
203
204
    /**
205
     * Determines what calling a request action (i.e. search()) will return
206
     *
207
     * If set to false, as is by default, the response class alone will be returned
208
     * If set to true, the whole request class will be passed back. This allows the
209
     * user to access the pagination methods
210
     * @var bool
211
     */
212
    private $returnRequestClass = false;
213
214
    /**
215
     * Response from UnionCloud
216
     *
217
     * This is populated by the $this->call() function
218
     *
219
     * @var BaseResponse
220
     */
221
    private $response;
222
223
224
225
226
227
228
229
230
231
232
233
    /*
234
    |--------------------------------------------------------------------------
235
    | Construct && class-wide helpers
236
    |--------------------------------------------------------------------------
237
    |
238
    | Save necessary configurations from the UnionCloud Wrapper. A
239
    |
240
    */
241
242
    /**
243
     * BaseRequest constructor.
244
     *
245
     * Saves the given authenticator and configuration
246
     *
247
     * @param Authentication $authentication
248
     * @param Configuration $configuration
249
     * @param string $responseClass
250
     */
251
    public function __construct($authentication, $configuration, $responseClass)
252
    {
253
        $this->authentication = $authentication;
254
        $this->configuration = $configuration;
255
        $this->setResponseClass($responseClass);
256
    }
257
258
    /**
259
     * Returns an instance of the child class, or of itself if
260
     * the child class hasn't implemented the getInstance method
261
     *
262
     * @return BaseRequest
263
     */
264
    private function getChildInstance()
265
    {
266
        if (method_exists($this, 'getInstance'))
267
        {
268
            return $this->getInstance();
269
        }
270
        return $this;
271
    }
272
273
274
275
276
    /*
277
   |--------------------------------------------------------------------------
278
   | User options for APIs
279
   |--------------------------------------------------------------------------
280
   |
281
   | These can be used by the end user to alter default options when creating
282
   | API calls.
283
   |
284
   */
285
286
287
    /**
288
     * Allow the user to set the mode of the API
289
     *
290
     * @param $mode
291
     *
292
     * @return $this
293
     */
294
    public function setMode($mode)
295
    {
296
        $this->mode = $mode;
297
        return $this;
298
    }
299
300
301
    /**
302
     * Enable pagination from the user side.
303
     *
304
     * Will tell the UnionCloud request that the
305
     * request class should be returned, as opposed to the response class
306
     *
307
     * @return $this UserRequest class
308
     */
309
    public function paginate()
310
    {
311
        $this->returnRequestClass = true;
312
        return $this->getChildInstance();
313
    }
314
315
    /**
316
     * Set the page number
317
     *
318
     * @param $page
319
     * @throws IncorrectRequestParameterException
320
     *
321
     * @return $this
322
     */
323
    public function setPage($page)
324
    {
325
        if (!is_int($page))
326
        {
327
            throw new IncorrectRequestParameterException('Page must be an integer', 400);
328
        }
329
        $this->page = $page;
330
331
        return $this->getChildInstance();
332
    }
333
334
335
336
337
338
339
    /*
340
   |--------------------------------------------------------------------------
341
   | API Endpoint Construction
342
   |--------------------------------------------------------------------------
343
   |
344
   | To make it easy to construct API Endpoints, a supply of helper functions
345
   | have been created.
346
   |
347
   */
348
349
    /**
350
     * Allows the API endpoint to support pagination
351
     *
352
     * Call this when defining an API endpoint.
353
     *
354
     * @return void
355
     */
356
    protected function enablePagination()
357
    {
358
        $this->paginates = true;
359
        // TODO Enable supoport for number of records per page
360
    }
361
362
    /**
363
     * Allow an API to use the ?mode= query
364
     *
365
     * @param string $mode
366
     */
367
    protected function enableMode()
368
    {
369
        $this->useMode = true;
370
    }
371
372
    /**
373
     * Allow an API to use the updated_at_before and updated_at_after parameters
374
     */
375
    protected function enableTimes()
376
    {
377
        // TODO Implement the enableTimes method
378
    }
379
380
    /**
381
     * Set the body as an array
382
     *
383
     * @param $body
384
     */
385
    protected function setBody($body)
386
    {
387
        $this->body = $body;
388
    }
389
390
    /**
391
     * Set the endpoint for the API request
392
     *
393
     * @param string $endPoint
394
     */
395
    protected function setEndpoint($endPoint)
396
    {
397
        $this->endPoint = $endPoint;
398
    }
399
400
    /**
401
     * Set the method for the API
402
     *
403
     * @param $method
404
     */
405
    protected function setMethod($method)
406
    {
407
        $this->method = $method;
408
    }
409
410
    /**
411
     * Set the content type
412
     *
413
     * You may pass in full content types, or use the shortcuts
414
     *      'json' => 'application/json',
415
     *      'form' => 'application//x-www-form-urlencoded'
416
     *
417
     * @param $contentType
418
     */
419
    protected function setContentType($contentType)
420
    {
421
        if ($contentType === 'json') { $contentType = 'application/json'; }
422
        elseif ($contentType === 'form') { $contentType = 'application/x-www-form-urlencoded'; }
423
        $this->contentType = $contentType;
424
    }
425
426
    /**
427
     * Set the class to handle the response
428
     *
429
     * This must implement IResponse
430
     *
431
     * @param $responseClass
432
     */
433
    protected function setResponseClass($responseClass)
434
    {
435
        $this->responseClass = $responseClass;
436
    }
437
438
    /**
439
     * A shortcut method for setting all required parameters in one function call
440
     *
441
     * @param string $endpoint e.g. 'users/search'
442
     * @param string $method
443
     * @param array|null $body If you don't include it in a data array, we will
444
     */
445
    protected function setAPIParameters($endpoint,$method,$body=null)
446
    {
447
        $this->setEndpoint($endpoint);
448
        $this->setMethod($method);
449
        if($body !== null)
450
        {
451
            $this->setBody((array_key_exists('data', $body)?$body:array('data'=>$body)));
452
        }
453
    }
454
455
    /**
456
     * Adds a query parameter to the URL of the request
457
     *
458
     * @param string $key Key of the query parameter
459
     * @param string $value Value of the query parameter
460
     */
461
    protected function addQueryParameter($key, $value)
462
    {
463
        $this->queryParameters[$key] = $value;
464
    }
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
    /*
483
    |--------------------------------------------------------------------------
484
    | Making API Calls
485
    |--------------------------------------------------------------------------
486
    |
487
    | A set of functions used in the creation of an API call
488
    |
489
    */
490
491
    ###################   Making the request    #######################
492
493
    /**
494
     * Handles making an API call
495
     *
496
     * Set all the parameters before calling this function.
497
     *
498
     * It will simply populate the property $this->response with the
499
     * response from UnionCloud
500
     *
501
     * @throws BaseResponseException
502
     * @throws RequestHistoryNotFound
503
     * @throws \GuzzleHttp\Exception\GuzzleException
504
     */
505
    protected function call()
506
    {
507
        // TODO throw error if user requested pagination but it isn't activated
508
509
        // Build up Guzzle HTTP Options, including authentication
510
        $options = $this->authentication->addAuthentication($this->getDefaultGuzzleOptions(), $this->configuration);
511
512
        // Create a client
513
        $client = $this->createClient();
514
515
        try {
516
            $request = new Request(
517
                $this->getMethod(),
518
                $this->getFullURL()
519
            );
520
            $client->send($request, $options);
521
        } catch (\Exception $e)
522
        {
523
            // TODO extract any errors from the response itself
524
            throw new BaseResponseException('Something went wrong', 500, $e);
525
        }
526
527
        // Extract the history
528
        if (!array_key_exists(0, $this->container))
529
        {
530
            throw new RequestHistoryNotFound('Request History wasn\'t recorded', 500);
531
        }
532
        try {
533
            $this->debugRequest = $this->container[0]['request'];
534
            $this->debugResponse = $this->container[0]['response'];
535
            $this->requestOptions = $this->container[0]['options'];
536
        } catch (\Exception $e) {
537
            throw new RequestHistoryNotFound('The element wasn\'t found in the history array', 500, $e);
538
        }
539
540
        // TODO Ensure this method doesn't use the debug variables. That way these don't have to be recorded if not debugging
541
        $this->processResponse($this->responseClass);
542
    }
543
544
545
    ###################    Creating the Guzzle Options    #######################
546
547
    /**
548
     * Gets the default GuzzleHTTP array
549
     *
550
     * @return array
551
     */
552
    private function getDefaultGuzzleOptions()
553
    {
554
        $options = [
555
            'headers' => [
556
                "User-Agent" => "Twigger-UnionCloud-API-Wrapper",
557
                "Content-Type" => $this->getContentType(),
558
            ],
559
            'http_errors' => true,
560
            'verify' => __DIR__.'/../../unioncloud.pem',
561
            'debug' => false
562
        ];
563
        if ($this->getContentType() === 'application/x-www-form-urlencoded')
564
        {
565
            $options['form_params'] = $this->getBody();
566
        } elseif ($this->getContentType() === 'application/json' && $this->getBody()) {
567
            $options["body"] = json_encode($this->getBody());
568
        }
569
570
        return $options;
571
    }
572
573
    /**
574
     * Get the body to put into the request
575
     *
576
     * @return array
577
     */
578
    private function getBody()
579
    {
580
        return $this->body;
581
    }
582
583
    /**
584
     * Get the content type
585
     *
586
     * Will return application\x-www-form-urlencoded if a POST request is made, or
587
     * application/json otherwise.
588
     *
589
     * Can be overridden by using the $this->setContentType function
590
     *
591
     * @return string
592
     */
593
    private function getContentType()
594
    {
595
        if (!$this->contentType)
596
        {
597
            if ($this->getMethod() === 'POST') { return 'application/x-www-form-urlencoded'; }
598
            else { return 'application/json'; }
599
        }
600
        return $this->contentType;
601
    }
602
603
604
    ###################    Create a Client    #######################
605
606
    /**
607
     * Creates a client
608
     *
609
     * Creates a default client with the user specified Base URL.
610
     *
611
     * Will also implement history middleware if debug is on
612
     *
613
     * @return Client
614
     */
615
    private function createClient()
616
    {
617
        $history = Middleware::history($this->container);
618
        $stack = HandlerStack::create();
619
        $stack->push($history);
620
621
622
        $client = new Client([
623
            'base_uri' => $this->configuration->getBaseURL(),
624
            'handler' => $stack,
625
        ]);
626
627
        return $client;
628
    }
629
630
631
    ###################    Create the API request    #######################
632
633
    /**
634
     * Gets the method for the request
635
     *
636
     * @return string
637
     */
638
    private function getMethod()
639
    {
640
        return $this->method;
641
    }
642
643
    /**
644
     * Get the full URL
645
     *
646
     * This consists of:
647
     *      /api/endpoint?query
648
     *
649
     * @return string
650
     */
651
    private function getFullURL()
652
    {
653
        $url = '/api/'.$this->getEndPoint();
654
        if (($parameters = $this->getQueryParameters()) !== null)
655
        {
656
            $url .= '?'.http_build_query($parameters);
657
        }
658
        return $url;
659
    }
660
661
    /**
662
     * Get the endpoint for the API
663
     *
664
     * @return string
665
     */
666
    private function getEndPoint()
667
    {
668
        return $this->endPoint;
669
    }
670
671
    /**
672
     * Get the query parameters for the API URL
673
     *
674
     * @return array|null
675
     */
676
    private function getQueryParameters()
677
    {
678
        $queryParameters = $this->queryParameters;
679
680
        // Add parameters set through settings
681
        if ($this->paginates)
682
        {
683
            $queryParameters['page'] = $this->page;
684
        }
685
        if ($this->useMode)
686
        {
687
            $queryParameters['mode'] = $this->mode;
688
        }
689
        if (count($queryParameters) === 0)
690
        {
691
            return null;
692
        }
693
694
        return $queryParameters;
695
    }
696
697
698
699
700
701
702
703
704
705
706
707
708
    /*
709
    |--------------------------------------------------------------------------
710
    | Pagination
711
    |--------------------------------------------------------------------------
712
    |
713
    | A set of functions to enable pagination between results
714
    |
715
    */
716
717
718
    /**
719
     * Add one to the page
720
     *
721
     * @return $this
722
     */
723
    protected function addPage()
724
    {
725
        $this->page++;
726
727
        return $this->getChildInstance();
728
    }
729
730
    /**
731
     * Take one from the page
732
     *
733
     * @return $this
734
     */
735
    protected function minusPage()
736
    {
737
        $this->page--;
738
739
        return $this->getChildInstance();
740
    }
741
742
    /**
743
     * Return the BaseRequest populated with the next page in the pagination.
744
     *
745
     * @return $this
746
     *
747
     * @throws BaseResponseException
748
     * @throws PageNotFoundException
749
     * @throws RequestHistoryNotFound
750
     * @throws \GuzzleHttp\Exception\GuzzleException
751
     */
752
    public function next()
753
    {
754
        if (!$this->response instanceof IResponse || $this->page >= $this->response->getTotalPages())
755
        {
756
            throw new PageNotFoundException();
757
        }
758
        $this->addPage();
759
        $this->call();
760
        return $this->getChildInstance();
761
    }
762
763
    /**
764
     * Return the BaseRequest populated with the previous page in the pagination.
765
     *
766
     * @return BaseRequest
767
     *
768
     * @throws BaseResponseException
769
     * @throws PageNotFoundException
770
     * @throws RequestHistoryNotFound
771
     * @throws \GuzzleHttp\Exception\GuzzleException
772
     */
773
    public function previous()
774
    {
775
        if (!$this->response instanceof IResponse || $this->page <= 1) {
776
            throw new PageNotFoundException();
777
        }
778
        $this->minusPage();
779
        $this->call();
780
        return $this->getChildInstance();
781
    }
782
783
    /**
784
     * Get all records in the API.
785
     *
786
     * If you have many records, this API may take a long time. We suggest
787
     * implementing a timeout, or checking there aren't too many rectords
788
     *
789
     * @return ResourceCollection
790
     *
791
     * @throws BaseResponseException
792
     * @throws RequestHistoryNotFound
793
     * @throws \GuzzleHttp\Exception\GuzzleException
794
     */
795
    public function getAllPages()
796
    {
797
        $resourceCollection = new ResourceCollection();
798
        $resourceCollection->addResources($this->response->get()->toArray());
799
        $this->addPage();
800
        while ($this->page <= $this->response->getTotalPages())
801
        {
802
            $this->call();
803
            $resourceCollection->addResources($this->response->get()->toArray());
804
            $this->addPage();
805
        }
806
        return $resourceCollection;
807
    }
808
809
810
811
812
813
814
815
816
817
818
819
    /*
820
    |--------------------------------------------------------------------------
821
    | Processing the Response
822
    |--------------------------------------------------------------------------
823
    |
824
    | A few functions to help process the response after an API call
825
    |
826
    */
827
828
829
    /**
830
     * Populate the $response property of the request
831
     *
832
     * Calls the construct of the chosen response handler,
833
     *
834
     * @param BaseResponse
835
     *
836
     * @throws BaseResponseException
837
     */
838
    private function processResponse($responseHandler)
839
    {
840
        if (!is_subclass_of($responseHandler, BaseResponse::class)) {
841
            throw new BaseResponseException('The response handler must extend BaseResponse', 500);
842
        }
843
844
845
        $processedResponse = new $responseHandler(
846
            $this->debugResponse,
847
            $this->debugRequest,
848
            $this->requestOptions
849
        );
850
851
        $this->response = $processedResponse;
852
    }
853
854
855
856
857
858
859
860
861
862
863
864
    /*
865
    |--------------------------------------------------------------------------
866
    | Returning the Response
867
    |--------------------------------------------------------------------------
868
    |
869
    | A few functions to help the user get what they need
870
    |
871
    */
872
873
    /**
874
     * Determine what to return to the user
875
     *
876
     * Return the request, a debugged response or a normal response
877
     *
878
     * @return $this|BaseResponse
879
     */
880
    protected function getReturnDetails()
881
    {
882
        if ($this->returnRequestClass)
883
        {
884
            $this->container = [];
885
            return $this;
886
        } else {
887
            if (!$this->configuration->getDebug())
888
            {
889
                $this->response->removeDebugOptions();
890
            }
891
            return $this->response;
892
        }
893
    }
894
895
    /**
896
     * Return just the collection of resources
897
     *
898
     * @return ResourceCollection\
899
     *
900
     * @throws ResponseMustInheritIResponse
901
     */
902
    public function get()
903
    {
904
        return $this->getResponse()->get();
0 ignored issues
show
Bug introduced by
The method get() does not exist on Twigger\UnionCloud\API\Response\IResponse. Since it exists in all sub-types, consider adding an abstract or default implementation to Twigger\UnionCloud\API\Response\IResponse. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

904
        return $this->getResponse()->/** @scrutinizer ignore-call */ get();
Loading history...
905
    }
906
907
    /**
908
     * Get the whole response class
909
     *
910
     * @return BaseResponse|IResponse
911
     *
912
     * @throws ResponseMustInheritIResponse
913
     */
914
    public function getResponse()
915
    {
916
        if (!$this->response instanceof IResponse)
917
        {
918
            throw new ResponseMustInheritIResponse();
919
        }
920
        return $this->response;
921
    }
922
923
924
}