summaryrefslogtreecommitdiff
path: root/server/vendor/php-opencloud/common/src/Common/Api/Operator.php
blob: 4325b69ddb1a3f5abb812a2dff97202df38e5862 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<?php

namespace OpenCloud\Common\Api;

use function GuzzleHttp\uri_template;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\Promise;
use OpenCloud\Common\Resource\ResourceInterface;
use OpenCloud\Common\Transport\RequestSerializer;
use Psr\Http\Message\ResponseInterface;

/**
 * {@inheritDoc}
 */
abstract class Operator implements OperatorInterface
{
    /** @var ClientInterface */
    protected $client;

    /** @var ApiInterface */
    protected $api;

    /**
     * {@inheritDoc}
     */
    public function __construct(ClientInterface $client, ApiInterface $api)
    {
        $this->client = $client;
        $this->api = $api;
    }

    /**
     * Magic method for dictating how objects are rendered when var_dump is called.
     * For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are
     * removed to provide easier access to normal state, such as resource attributes.
     *
     * @codeCoverageIgnore
     * @return array
     */
    public function __debugInfo()
    {
        $excludedVars = ['client', 'errorBuilder', 'api'];

        $output = [];

        foreach (get_object_vars($this) as $key => $val) {
            if (!in_array($key, $excludedVars)) {
                $output[$key] = $val;
            }
        }

        return $output;
    }

    /**
     * Retrieves a populated Operation according to the definition and values provided. A
     * HTTP client is also injected into the object to allow it to communicate with the remote API.
     *
     * @param array $definition The data that dictates how the operation works
     *
     * @return Operation
     */
    public function getOperation(array $definition)
    {
        return new Operation($definition);
    }

    protected function sendRequest(Operation $operation, array $userValues = [], $async = false)
    {
        $operation->validate($userValues);

        $options = (new RequestSerializer)->serializeOptions($operation, $userValues);
        $method = $async ? 'requestAsync' : 'request';
        $uri = uri_template($operation->getPath(), $userValues);

        return $this->client->$method($operation->getMethod(), $uri, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function execute(array $definition, array $userValues = [])
    {
        return $this->sendRequest($this->getOperation($definition), $userValues);
    }

    /**
     * {@inheritDoc}
     */
    public function executeAsync(array $definition, array $userValues = [])
    {
        return $this->sendRequest($this->getOperation($definition), $userValues, true);
    }

    /**
     * {@inheritDoc}
     */
    public function model($class, $data = null)
    {
        $model = new $class($this->client, $this->api);

        // @codeCoverageIgnoreStart
        if (!$model instanceof ResourceInterface) {
            throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class));
        }
        // @codeCoverageIgnoreEnd

        if ($data instanceof ResponseInterface) {
            $model->populateFromResponse($data);
        } elseif (is_array($data)) {
            $model->populateFromArray($data);
        }

        return $model;
    }

    /**
     * Will create a new instance of this class with the current HTTP client and API injected in. This
     * is useful when enumerating over a collection since multiple copies of the same resource class
     * are needed.
     *
     * @return static
     */
    public function newInstance()
    {
        return new static($this->client, $this->api);
    }

    /**
     * @return \GuzzleHttp\Psr7\Uri
     */
    protected function getHttpBaseUrl()
    {
        return $this->client->getConfig('base_uri');
    }

    /**
     * Magic method which intercepts async calls, finds the sequential version, and wraps it in a
     * {@see Promise} object. In order for this to happen, the called methods need to be in the
     * following format: `createAsync`, where `create` is the sequential method being wrapped.
     *
     * @param $methodName The name of the method being invoked.
     * @param $args       The arguments to be passed to the sequential method.
     *
     * @throws \RuntimeException If method does not exist
     *
     * @return Promise
     */
    public function __call($methodName, $args)
    {
        $e = function ($name) {
            return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name));
        };

        if (substr($methodName, -5) === 'Async') {
            $realMethod = substr($methodName, 0, -5);
            if (!method_exists($this, $realMethod)) {
                throw $e($realMethod);
            }

            $promise = new Promise(
                function () use (&$promise, $realMethod, $args) {
                    $value = call_user_func_array([$this, $realMethod], $args);
                    $promise->resolve($value);
                }
            );

            return $promise;
        }

        throw $e($methodName);
    }
}