Completed
Push — async-first ( 0001b9 )
by
unknown
01:55
created

Connector::__call()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 5
nc 2
nop 2
1
<?php
2
namespace Communibase;
3
4
use Communibase\Logging\QueryLogger;
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\ClientException;
7
use GuzzleHttp\Promise\Promise;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
11
/**
12
 * Communibase (https://communibase.nl) data Connector for PHP
13
 *
14
 * For more information see https://communibase.nl
15
 *
16
 * Following are IDE hints for sync method versions:
17
 *
18
 * @method string getTemplateSync(string $entityType) Returns all the fields according to the definition.
19
 * @method array getByIdSync(string $entityType, string $id) Get an entity by id
20
 * @method array getByIdsSync(string $entityType, array $ids, array $params = []) Get an array of entities by their ids
21
 * @method array getAllSync(string $entityType, array $params) Get all entities of a certain type
22
 * @method array getIdSync(string $entityType, array $selector) Get the id of an entity based on a search
23
 * @method array getHistorySync(string $entityType, string $id) Returns an array of the history for the entity
24
 * @method array destroySync(string $entityType, string $id) Delete something from Communibase
25
 *
26
 * @package Communibase
27
 * @author Kingsquare ([email protected])
28
 * @copyright Copyright (c) Kingsquare BV (http://www.kingsquare.nl)
29
 * @license http://opensource.org/licenses/MIT The MIT License (MIT)
30
 */
31
class Connector implements ConnectorInterface
32
{
33
34
    /**
35
     * The official service URI; can be overridden via the constructor
36
     *
37
     * @var string
38
     */
39
    const SERVICE_PRODUCTION_URL = 'https://api.communibase.nl/0.1/';
40
41
    /**
42
     * The API key which is to be used for the api.
43
     * Is required to be set via the constructor.
44
     *
45
     * @var string
46
     */
47
    private $apiKey;
48
49
    /**
50
     * The url which is to be used for this connector. Defaults to the production url.
51
     * Can be set via the constructor.
52
     *
53
     * @var string
54
     */
55
    private $serviceUrl;
56
57
    /**
58
     * @var array of extra headers to send with each request
59
     */
60
    private $extraHeaders = [];
61
62
    /**
63
     * @var QueryLogger
64
     */
65
    private $logger;
66
67
    /**
68
     * @var ClientInterface
69
     */
70
    private $client;
71
72
    /**
73
     * Create a new Communibase Connector instance based on the given api-key and possible serviceUrl
74
     *
75
     * @param string $apiKey The API key for Communibase
76
     * @param string $serviceUrl The Communibase API endpoint; defaults to self::SERVICE_PRODUCTION_URL
77
     * @param ClientInterface $client An optional GuzzleHttp Client (or Interface for mocking)
78
     */
79
    public function __construct(
80
            $apiKey,
81
            $serviceUrl = self::SERVICE_PRODUCTION_URL,
82
            ClientInterface $client = null
83
    ) {
84
        $this->apiKey = $apiKey;
85
        $this->serviceUrl = $serviceUrl;
86
        $this->client = $client;
87
    }
88
89
    /**
90
     * Returns an array that has all the fields according to the definition in Communibase.
91
     *
92
     * @param string $entityType
93
     *
94
     * @return Promise of result
95
     *
96
     * @throws Exception
97
     */
98
    public function getTemplate($entityType)
99
    {
100
        $params = [
101
            'fields' => 'attributes.title',
102
            'limit' => 1,
103
        ];
104
105
        return $this->search('EntityType', ['title' => $entityType], $params)->then(function ($definition) {
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->search('En..., 'title')), null); }); (GuzzleHttp\Promise\PromiseInterface) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getTemplate of type array.

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...
106
            return array_fill_keys(array_merge(['_id'], array_column($definition[0]['attributes'], 'title')), null);
107
        });
108
    }
109
110
    /**
111
     * Get a single Entity by its id
112
     *
113
     * @param string $entityType
114
     * @param string $id
115
     * @param array $params (optional)
116
     * @param string|null $version
117
     *
118
     * @return Promise of result
119
     *
120
     * @return array entity
121
     *
122
     * @throws Exception
123
     */
124
    public function getById($entityType, $id, array $params = [], $version = null)
125
    {
126
        if (empty($id)) {
127
            throw new Exception('Id is empty');
128
        }
129
        if (!$this->isIdValid($id)) {
130
            throw new Exception('Id is invalid, please use a correctly formatted id');
131
        }
132
133
        return ($version === null)
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $version === null...' . $version, $params); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getById of type array.

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...
134
            ? $this->doGet($entityType . '.json/crud/' . $id, $params)
135
            : $this->doGet($entityType . '.json/history/' . $id . '/' . $version, $params);
136
    }
137
138
    /**
139
     * NOTE not yet async
140
     *
141
     * Get a single Entity by a ref-string
142
     *
143
     * @param string $ref
144
     * @param array $parentEntity (optional)
145
     *
146
     * @return array the referred Entity data
147
     *
148
     * @throws Exception
149
     */
150
    public function getByRef($ref, array $parentEntity = [])
151
    {
152
        $refParts = explode('.', $ref);
153
        if ($refParts[0] !== 'parent') {
154
            $entityParts = explode('|', $refParts[0]);
155
            $parentEntity = $this->getById($entityParts[0], $entityParts[1]);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getById($entityParts[0], $entityParts[1]); of type GuzzleHttp\Promise\Promise adds the type GuzzleHttp\Promise\Promise to the return on line 158 which is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getByRef of type array.
Loading history...
156
        }
157
        if (empty($refParts[1])) {
158
            return $parentEntity;
159
        }
160
        $propertyParts = explode('|', $refParts[1]);
161
        foreach ($parentEntity[$propertyParts[0]] as $subEntity) {
162
            if ($subEntity['_id'] === $propertyParts[1]) {
163
                return $subEntity;
164
            }
165
        }
166
        throw new Exception('Could not find the referred Entity');
167
    }
168
169
    /**
170
     * Get an array of entities by their ids
171
     *
172
     * @param string $entityType
173
     * @param array $ids
174
     * @param array $params (optional)
175
     *
176
     * @return Promise of result
177
     */
178
    public function getByIds($entityType, array $ids, array $params = [])
179
    {
180
        $validIds = array_values(array_unique(array_filter($ids, [$this, 'isIdValid'])));
181
182
        if (empty($validIds)) {
183
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type documented by Communibase\Connector::getByIds of type GuzzleHttp\Promise\Promise.

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...
184
        }
185
186
        $doSortByIds = empty($params['sort']);
187
188
        return $this->search($entityType, ['_id' => ['$in' => $validIds]], $params)->then(function ($results) use ($doSortByIds, $validIds) {
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->search($en...y($result); }); }); (GuzzleHttp\Promise\PromiseInterface) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getByIds of type array.

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...
189
            if (!$doSortByIds) {
190
                return $results;
191
            }
192
193
            $flipped = array_flip($validIds);
194
            foreach ($results as $result) {
195
                $flipped[$result['_id']] = $result;
196
            }
197
            return array_filter(array_values($flipped), function ($result) {
198
                return is_array($result) && !empty($result);
199
            });
200
        });
201
    }
202
203
    /**
204
     * Get all entities of a certain type
205
     *
206
     * @param string $entityType
207
     * @param array $params (optional)
208
     *
209
     * @return Promise of result
210
     */
211
    public function getAll($entityType, array $params = [])
212
    {
213
        return $this->doGet($entityType . '.json/crud/', $params);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doGet($ent....json/crud/', $params); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getAll of type array|null.

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...
214
    }
215
216
    /**
217
     * Get result entityIds of a certain search
218
     *
219
     * @param string $entityType
220
     * @param array $selector (optional)
221
     * @param array $params (optional)
222
     *
223
     * @return Promise of result
224
     */
225
    public function getIds($entityType, array $selector = [], array $params = [])
226
    {
227
        $params['fields'] = '_id';
228
229
        return $this->search($entityType, $selector, $params)->then(function ($results) {
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->search($en...n($results, '_id'); }); (GuzzleHttp\Promise\PromiseInterface) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getIds of type array.

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...
230
            return array_column($results, '_id');
231
        });
232
    }
233
234
    /**
235
     * Get the id of an entity based on a search
236
     *
237
     * @param string $entityType i.e. Person
238
     * @param array $selector (optional) i.e. ['firstName' => 'Henk']
239
     *
240
     * @return Promise of result
241
     */
242
    public function getId($entityType, array $selector = [])
243
    {
244
        $params = ['limit' => 1];
245
        $ids = (array)$this->getIds($entityType, $selector, $params);
246
247
        return array_shift($ids);
248
    }
249
250
    /**
251
     * Returns an array of the history for the entity with the following format:
252
     *
253
     * <code>
254
     *  [
255
     *        [
256
     *            'updatedBy' => '', // name of the user
257
     *            'updatedAt' => '', // a string according to the DateTime::ISO8601 format
258
     *            '_id' => '', // the ID of the entity which can ge fetched seperately
259
     *        ],
260
     *        ...
261
     * ]
262
     * </code>
263
     *
264
     * @param string $entityType
265
     * @param string $id
266
     *
267
     * @return Promise of result
268
     *
269
     * @throws Exception
270
     */
271
    public function getHistory($entityType, $id)
272
    {
273
        return $this->doGet($entityType . '.json/history/' . $id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doGet($ent....json/history/' . $id); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getHistory of type array.

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...
274
    }
275
276
    /**
277
     * Search for the given entity by optional passed selector/params
278
     *
279
     * @param string $entityType
280
     * @param array $querySelector
281
     * @param array $params (optional)
282
     *
283
     * @return Promise of result
284
     *
285
     * @throws Exception
286
     */
287
    public function search($entityType, array $querySelector, array $params = [])
288
    {
289
        return $this->doPost($entityType . '.json/search', $params, $querySelector);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doPost($en...arams, $querySelector); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::search of type array.

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...
290
    }
291
292
    /**
293
     * This will save an entity in Communibase. When a _id-field is found, this entity will be updated
294
     *
295
     * NOTE: When updating, depending on the Entity, you may need to include all fields.
296
     *
297
     * @param string $entityType
298
     * @param array $properties - the to-be-saved entity data
299
     *
300
     * @returns Promise of result
301
     *
302
     * @throws Exception
303
     */
304
    public function update($entityType, array $properties)
305
    {
306
        $isNew = empty($properties['_id']);
307
308
        return $this->{$isNew ? 'doPost' : 'doPut'}(
309
            $entityType . '.json/crud/' . ($isNew ? '' : $properties['_id']),
310
            [],
311
            $properties
312
        );
313
    }
314
315
    /**
316
     * Finalize an invoice by adding an invoiceNumber to it.
317
     * Besides, invoice items will receive a "generalLedgerAccountNumber".
318
     * This number will be unique and sequential within the "daybook" of the invoice.
319
     *
320
     * NOTE: this is Invoice specific
321
     *
322
     * @param string $entityType
323
     * @param string $id
324
     *
325
     * @return Promise of result
326
     *
327
     * @throws Exception
328
     */
329
    public function finalize($entityType, $id)
330
    {
331
        if ($entityType !== 'Invoice') {
332
            throw new Exception('Cannot call finalize on ' . $entityType);
333
        }
334
335
        return $this->doPost($entityType . '.json/finalize/' . $id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doPost($en...json/finalize/' . $id); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::finalize of type array.

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...
336
    }
337
338
    /**
339
     * Delete something from Communibase
340
     *
341
     * @param string $entityType
342
     * @param string $id
343
     *
344
     * @return Promise of result
345
     */
346
    public function destroy($entityType, $id)
347
    {
348
        return $this->doDelete($entityType . '.json/crud/' . $id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doDelete($.... '.json/crud/' . $id); (GuzzleHttp\Promise\Promise) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::destroy of type array.

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...
349
    }
350
351
    /**
352
     * Get the binary contents of a file by its ID
353
     *
354
     * NOTE: for meta-data like filesize and mimetype, one can use the getById()-method.
355
     *
356
     * @param string $id id string for the file-entity
357
     *
358
     * @return StreamInterface Binary contents of the file. Since the stream can be made a string this works like a charm!
359
     *
360
     * @throws Exception
361
     */
362
    public function getBinary($id)
363
    {
364
        if (!$this->isIdValid($id)) {
365
            throw new Exception('Invalid $id passed. Please provide one.');
366
        }
367
368
        return $this->call('get', ['File.json/binary/' . $id])->then(function (ResponseInterface $response) {
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->call('get'...esponse->getBody(); }); (GuzzleHttp\Promise\PromiseInterface) is incompatible with the return type declared by the interface Communibase\ConnectorInterface::getBinary of type Psr\Http\Message\StreamInterface.

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...
369
            return $response->getBody();
370
        });
371
    }
372
373
    /**
374
     * Uploads the contents of the resource (this could be a file handle) to Communibase
375
     *
376
     * @param StreamInterface $resource
377
     * @param string $name
378
     * @param string $destinationPath
379
     * @param string $id
380
     *
381
     * @return array|mixed
382
     * @throws Exception
383
     */
384
    public function updateBinary(StreamInterface $resource, $name, $destinationPath, $id = '')
385
    {
386
        $metaData = ['path' => $destinationPath];
387
        if (empty($id)) {
388
            $options = [
389
                'multipart' => [
390
                    [
391
                        'name' => 'File',
392
                        'filename' => $name,
393
                        'contents' => $resource
394
                    ],
395
                    [
396
                        'name' => 'metadata',
397
                        'contents' => json_encode($metaData),
398
                    ]
399
                ]
400
            ];
401
402
            return $this->call('post', ['File.json/binary', $options])->then(function (ResponseInterface $response) {
403
                return $this->parseResult($response->getBody(), $response->getStatusCode());
404
            });
405
406
        }
407
408
        return $this->doPut('File.json/crud/' . $id, [], [
409
                'filename' => $name,
410
                'length' => $resource->getSize(),
411
                'uploadDate' => date('c'),
412
                'metadata' => $metaData,
413
                'content' => base64_encode($resource->getContents()),
414
        ]);
415
    }
416
417
    /**
418
     * MAGIC for making sync requests
419
     *
420
     * @param string $name
421
     * @param array $arguments
422
     *
423
     * @return mixed
424
     */
425
    public function __call($name, $arguments)
426
    {
427
        if (preg_match('#(.*)Sync$#', $name, $matches) && is_callable([$this, $matches[1]])) {
428
            $promise = call_user_func_array([$this, $matches[1]], $arguments);
429
430
            /* @var Promise $promise */
431
            return $promise->wait(); // wait for response
432
        }
433
434
        // fallback to known methods
435
        return null;
436
    }
437
438
    /**
439
     * @param string $path
440
     * @param array $params
441
     * @param array $data
442
     *
443
     * @return Promise
444
     *
445
     * @throws Exception
446
     */
447
    protected function doGet($path, array $params = null, array $data = null)
448
    {
449
        return $this->getResult('GET', $path, $params, $data);
450
    }
451
452
    /**
453
     * @param string $path
454
     * @param array $params
455
     * @param array $data
456
     *
457
     * @return Promise
458
     *
459
     * @throws Exception
460
     */
461
    protected function doPost($path, array $params = null, array $data = null)
462
    {
463
        return $this->getResult('POST', $path, $params, $data);
464
    }
465
466
    /**
467
     * @param string $path
468
     * @param array $params
469
     * @param array $data
470
     *
471
     * @return Promise
472
     *
473
     * @throws Exception
474
     */
475
    protected function doPut($path, array $params = null, array $data = null)
476
    {
477
        return $this->getResult('PUT', $path, $params, $data);
478
    }
479
480
    /**
481
     * @param string $path
482
     * @param array $params
483
     * @param array $data
484
     *
485
     * @return Promise
486
     *
487
     * @throws Exception
488
     */
489
    protected function doDelete($path, array $params = null, array $data = null)
490
    {
491
        return $this->getResult('DELETE', $path, $params, $data);
492
    }
493
494
    /**
495
     * Process the request
496
     *
497
     * @param string $method
498
     * @param string $path
499
     * @param array $params
500
     * @param array $data
501
     *
502
     * @return Promise array i.e. [success => true|false, [errors => ['message' => 'this is broken', ..]]]
503
     *
504
     * @throws Exception
505
     */
506
    protected function getResult($method, $path, array $params = null, array $data = null)
507
    {
508
        if ($params === null) {
509
            $params = [];
510
        }
511
        $options = [
512
            'query' => $this->preParseParams($params),
513
        ];
514
        if (!empty($data)) {
515
            $options['json'] = $data;
516
        }
517
518
        return $this->call($method, [$path, $options])->then(function (ResponseInterface $response) {
519
520
            return $this->parseResult($response->getBody(), $response->getStatusCode());
521
522
        })->otherwise(function (\Exception $ex) {
523
524
            // GuzzleHttp\Exception\ClientException
525
            // Communibase\Exception
526
527
            if ($ex instanceof ClientException) {
528
529
                if ($ex->getResponse()->getStatusCode() !== 200) {
530
                    throw new Exception(
531
                        $ex->getMessage(),
532
                        $ex->getResponse()->getStatusCode(),
533
                        null,
534
                        []
535
                    );
536
                }
537
538
            }
539
540
            throw $ex;
541
542
        });
543
544
    }
545
546
    /**
547
     * @param array $params
548
     *
549
     * @return mixed
550
     */
551
    private function preParseParams(array $params)
552
    {
553
        if (!array_key_exists('fields', $params) || !is_array($params['fields'])) {
554
            return $params;
555
        }
556
557
        $fields = [];
558
        foreach ($params['fields'] as $index => $field) {
559
            if (!is_numeric($index)) {
560
                $fields[$index] = $field;
561
                continue;
562
            }
563
564
            $modifier = 1;
565
            $firstChar = substr($field, 0, 1);
566
            if ($firstChar == '+' || $firstChar == '-') {
567
                $modifier = $firstChar == '+' ? 1 : 0;
568
                $field = substr($field, 1);
569
            }
570
            $fields[$field] = $modifier;
571
        }
572
        $params['fields'] = $fields;
573
574
        return $params;
575
    }
576
577
    /**
578
     * Parse the Communibase result and if necessary throw an exception
579
     *
580
     * @param string $response
581
     * @param int $httpCode
582
     *
583
     * @return array
584
     *
585
     * @throws Exception
586
     */
587
    private function parseResult($response, $httpCode)
588
    {
589
        $result = json_decode($response, true);
590
591
        if (is_array($result)) {
592
            return $result;
593
        }
594
595
        throw new Exception('"' . $this->getLastJsonError() . '" in ' . $response, $httpCode);
596
    }
597
598
    /**
599
     * Error message based on the most recent JSON error.
600
     *
601
     * @see http://nl1.php.net/manual/en/function.json-last-error.php
602
     *
603
     * @return string
604
     */
605
    private function getLastJsonError()
606
    {
607
        $jsonLastError = json_last_error();
608
        $messages = [
609
                JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
610
                JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
611
                JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
612
                JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
613
                JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
614
        ];
615
616
        return (isset($messages[$jsonLastError]) ? $messages[$jsonLastError] : 'Empty response received');
617
    }
618
619
    /**
620
     * @param string $id
621
     *
622
     * @return bool
623
     */
624
    public static function isIdValid($id)
625
    {
626
        if (empty($id)) {
627
            return false;
628
        }
629
630
        if (preg_match('#[0-9a-fA-F]{24}#', $id) === 0) {
631
            return false;
632
        }
633
634
        return true;
635
    }
636
637
    /**
638
     * Generate a Communibase compatible ID, that consists of:
639
     *
640
     * a 4-byte timestamp,
641
     * a 3-byte machine identifier,
642
     * a 2-byte process id, and
643
     * a 3-byte counter, starting with a random value.
644
     *
645
     * @return string
646
     */
647
    public static function generateId()
648
    {
649
        static $inc = 0;
650
651
        $ts = pack('N', time());
652
        $m = substr(md5(gethostname()), 0, 3);
653
        $pid = pack('n', 1); //posix_getpid()
654
        $trail = substr(pack('N', $inc++), 1, 3);
655
656
        $bin = sprintf("%s%s%s%s", $ts, $m, $pid, $trail);
657
        $id = '';
658
        for ($i = 0; $i < 12; $i++) {
659
            $id .= sprintf("%02X", ord($bin[$i]));
660
        }
661
662
        return strtolower($id);
663
    }
664
665
    /**
666
     * Add extra headers to be added to each request
667
     *
668
     * @see http://php.net/manual/en/function.header.php
669
     *
670
     * @param array $extraHeaders
671
     */
672
    public function addExtraHeaders(array $extraHeaders)
673
    {
674
        $this->extraHeaders = array_change_key_case($extraHeaders, CASE_LOWER);
675
    }
676
677
    /**
678
     * @param QueryLogger $logger
679
     */
680
    public function setQueryLogger(QueryLogger $logger)
681
    {
682
        $this->logger = $logger;
683
    }
684
685
    /**
686
     * @return QueryLogger
687
     */
688
    public function getQueryLogger()
689
    {
690
        return $this->logger;
691
    }
692
693
    /**
694
     * @return \GuzzleHttp\Client
695
     * @throws Exception
696
     */
697
    protected function getClient()
698
    {
699
        if ($this->client instanceof ClientInterface) {
700
            return $this->client;
701
        }
702
703
        if (empty($this->apiKey)) {
704
            throw new Exception('Use of connector not possible without API key', Exception::INVALID_API_KEY);
705
        }
706
707
        $this->client = new \GuzzleHttp\Client([
708
            'base_uri' => $this->serviceUrl,
709
            'headers' => array_merge($this->extraHeaders, [
710
                'User-Agent' => 'Connector-PHP/2',
711
                'X-Api-Key' => $this->apiKey,
712
            ])
713
        ]);
714
715
        return $this->client;
716
    }
717
718
    /**
719
     * @param string $method
720
     * @param array $arguments
721
     *
722
     * @return Promise
723
     *
724
     * @throws Exception
725
     */
726
    private function call($method, array $arguments)
727
    {
728
        if (isset($this->extraHeaders['host'])) {
729
            $arguments[1]['headers']['Host'] = $this->extraHeaders['host'];
730
        }
731
732
        $idx = null; // the query index
733
        if ($this->getQueryLogger()) {
734
            $idx = $this->getQueryLogger()->startQuery($method . ' ' . reset($arguments), $arguments);
735
        }
736
737
        $promise = call_user_func_array([$this->getClient(), $method . 'Async'], $arguments);
738
        /* @var Promise $promise */
739
        return $promise->then(function ($response) use ($idx) {
740
741
            if ($this->getQueryLogger()) {
742
                $this->getQueryLogger()->stopQuery($idx);
743
            }
744
745
            return $response;
746
        });
747
    }
748
749
}
750