├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src └── Centrifugo ├── BatchRequest.php ├── BatchResponse.php ├── Centrifugo.php ├── ClientInterface.php ├── Clients ├── BaseClient.php ├── HttpClient.php ├── Redis │ ├── PredisTransport.php │ ├── RedisTransport.php │ └── TransportInterface.php └── RedisClient.php ├── Exceptions ├── CentrifugoAlreadySubscribedException.php ├── CentrifugoClientClosedException.php ├── CentrifugoException.php ├── CentrifugoInternalServerErrorException.php ├── CentrifugoInvalidMessageException.php ├── CentrifugoInvalidTokenException.php ├── CentrifugoLimitExceededException.php ├── CentrifugoMethodNotFoundException.php ├── CentrifugoNamespaceNotFoundException.php ├── CentrifugoNotAvailableException.php ├── CentrifugoPermissionDeniedException.php ├── CentrifugoResponseException.php ├── CentrifugoSendTimeoutException.php ├── CentrifugoTransportException.php └── CentrifugoUnauthorizedException.php ├── Request.php └── Response.php /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 3 | 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # User-specific stuff: 11 | # .idea/workspace.xml 12 | # .idea/tasks.xml 13 | # .idea/dictionaries 14 | 15 | # Sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | # .idea/uiDesigner.xml 21 | 22 | # Gradle: 23 | # .idea/gradle.xml 24 | # .idea/libraries 25 | 26 | # Mongo Explorer plugin: 27 | # .idea/mongoSettings.xml 28 | 29 | ## File-based project format: 30 | *.ipr 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | 49 | # Created by .ignore support plugin (hsz.mobi) 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Oleh Ozimok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-centrifugo - v2, [v1](https://github.com/oleh-ozimok/php-centrifugo/tree/v1.0) 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/oleh-ozimok/php-centrifugo/v/stable)](https://packagist.org/packages/oleh-ozimok/php-centrifugo) 4 | [![Total Downloads](https://poser.pugx.org/oleh-ozimok/php-centrifugo/downloads)](https://packagist.org/packages/oleh-ozimok/php-centrifugo) 5 | [![License](https://poser.pugx.org/oleh-ozimok/php-centrifugo/license)](https://packagist.org/packages/oleh-ozimok/php-centrifugo) 6 | 7 | PHP client for [Centrifugo](https://github.com/centrifugal/centrifugo) real-time messaging server 8 | 9 | ## Features 10 | 11 | * Support publishing messages via Redis engine API listener (publish, broadcast, unsubscribe, disconnect methods only) 12 | * Support client chain (Redis -> HTTP -> ...) as failover. If Redis down or method not supported by Redis engine API, client try send message via HTTP 13 | * Support batch requests 14 | * Support Predis 15 | 16 | ## Quick Examples 17 | 18 | ### Create Centrifugo client 19 | 20 | ```php 21 | connect('localhost'); 44 | $redisTransport = new RedisTransport($redis); 45 | 46 | // Or Predis transport 47 | 48 | $predis = new Predis\Client(['host' => 'localhost']); 49 | $redisTransport = new PredisTransport($predis); 50 | 51 | // Create Centrifugo RedisClient 52 | 53 | $centrifugoRedisClient = new RedisClient($redisTransport); 54 | $centrifugoRedisClient->setShardsNumber(12); 55 | 56 | // Add Centrifugo HttpClient as failover 57 | 58 | $centrifugoHttpClient = new HttpClient(); 59 | $centrifugoRedisClient->setFailover($centrifugoHttpClient); 60 | 61 | $centrifugo = new Centrifugo('http://example.com/api/', 'secret api key', $centrifugoRedisClient); 62 | ``` 63 | ### Send request to Centrifugo 64 | 65 | ```php 66 | 'Hello, world!']; 74 | 75 | try { 76 | //Send message into channel. 77 | $response = $centrifugo->publish($channel, $messageData); 78 | 79 | //Very similar to publish but allows to send the same data into many channels. 80 | $response = $centrifugo->broadcast([$channel], $messageData); 81 | 82 | //Unsubscribe user from channel. 83 | $response = $centrifugo->unsubscribe($channel, $userId); 84 | 85 | //Disconnect user by user ID. 86 | $response = $centrifugo->disconnect($userId); 87 | 88 | //Get channel presence information (all clients currently subscribed on this channel). 89 | $response = $centrifugo->presence($channel); 90 | 91 | //Get channel history information (list of last messages sent into channel). 92 | $response = $centrifugo->history($channel); 93 | 94 | //Get channels information (list of currently active channels). 95 | $response = $centrifugo->channels(); 96 | 97 | //Get stats information about running server nodes. 98 | $response = $centrifugo->stats(); 99 | 100 | //Get information about single Centrifugo node. 101 | $response = $centrifugo->node('http://node1.example.com/api/'); 102 | } catch (CentrifugoException $e) { 103 | // invalid response 104 | } 105 | ``` 106 | 107 | ### Send batch request 108 | 109 | ```php 110 | 'Hello, world!']; 118 | 119 | try { 120 | $requests[] = $centrifugo->request('publish', ['channel' => $channel, 'data' => $messageData]); 121 | $requests[] = $centrifugo->request('broadcast', ['channel' => $channel, 'data' => $messageData]); 122 | $requests[] = $centrifugo->request('unsubscribe', ['channel' => $channel, 'user' => $userId]); 123 | $requests[] = $centrifugo->request('disconnect', ['user' => $userId]); 124 | 125 | $batchResponse = $centrifugo->sendBatchRequest($requests); 126 | 127 | foreach ($batchResponse as $response) { 128 | if ($response->isError()) { 129 | // get error info 130 | $error = $response->getError(); 131 | } else { 132 | // get response data as array 133 | $responseData = $response->getDecodedBody(); 134 | } 135 | } 136 | } catch (CentrifugoException $e) { 137 | // invalid response 138 | } 139 | ``` 140 | 141 | ## Related Projects 142 | [centrifugo-bundle](https://github.com/kismia/centrifugo-bundle) (under development) 143 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oleh-ozimok/php-centrifugo", 3 | "type": "library", 4 | "description": "PHP client for Centrifugo real-time messaging server", 5 | "keywords": ["centrifugo", "real-time"], 6 | "license": "MIT", 7 | "homepage": "https://github.com/oleh-ozimok/php-centrifugo", 8 | "authors": [ 9 | { 10 | "name": "Oleh Ozimok", 11 | "email": "ozimok.oleg@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4.0", 16 | "ext-curl": "*" 17 | }, 18 | "require-dev": { 19 | "predis/predis": "^1.1" 20 | }, 21 | "suggest": { 22 | "predis/predis": "Enable Predis support" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Centrifugo\\": "src/Centrifugo" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Centrifugo/BatchRequest.php: -------------------------------------------------------------------------------- 1 | add($requests); 34 | } 35 | 36 | /** 37 | * @param mixed $request 38 | * 39 | * @return $this 40 | * @throws InvalidArgumentException 41 | */ 42 | public function add($request) 43 | { 44 | if (is_array($request)) { 45 | array_walk($request, [$this, __METHOD__]); 46 | 47 | return $this; 48 | } 49 | 50 | if (!$request instanceof Request) { 51 | throw new InvalidArgumentException('Argument for add() must be of type array or Request.'); 52 | } 53 | 54 | $this->requests[] = $request; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | public function toArray() 63 | { 64 | $array = []; 65 | foreach ($this->requests as $request) { 66 | $array[] = $request->toArray(); 67 | } 68 | 69 | return $array; 70 | } 71 | 72 | /** 73 | * @return ArrayIterator 74 | */ 75 | public function getIterator() 76 | { 77 | return new ArrayIterator($this->requests); 78 | } 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public function offsetSet($offset, $value) 84 | { 85 | $this->add($value); 86 | } 87 | 88 | /** 89 | * @inheritdoc 90 | */ 91 | public function offsetExists($offset) 92 | { 93 | return isset($this->requests[$offset]); 94 | } 95 | 96 | /** 97 | * @inheritdoc 98 | */ 99 | public function offsetUnset($offset) 100 | { 101 | unset($this->requests[$offset]); 102 | } 103 | 104 | /** 105 | * @inheritdoc 106 | */ 107 | public function offsetGet($offset) 108 | { 109 | return isset($this->requests[$offset]) ? $this->requests[$offset] : null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Centrifugo/BatchResponse.php: -------------------------------------------------------------------------------- 1 | getBody()); 29 | 30 | $this->setResponses($response->getDecodedBody()); 31 | } 32 | 33 | /** 34 | * @param array $responses 35 | */ 36 | public function setResponses(array $responses) 37 | { 38 | $this->responses = []; 39 | foreach ($responses as $key => $response) { 40 | $this->addResponse($key, $response); 41 | } 42 | } 43 | 44 | /** 45 | * Add a response to the list. 46 | * 47 | * @param int $key 48 | * @param array|null $response 49 | */ 50 | public function addResponse($key, array $response) 51 | { 52 | $originalRequest = isset($this->request[$key]) ? $this->request[$key] : null; 53 | $responseBody = isset($response['body']) ? $response['body'] : null; 54 | $responseError = isset($response['error']) ? $response['error'] : null; 55 | $responseMethod = isset($response['method']) ? $response['method'] : null; 56 | 57 | $this->responses[$key] = new Response($originalRequest, $responseBody, $responseError, $responseMethod); 58 | } 59 | 60 | /** 61 | * @return Response[] 62 | */ 63 | public function getResponses() 64 | { 65 | return $this->responses; 66 | } 67 | 68 | /** 69 | * @return Response 70 | */ 71 | public function shiftResponses() 72 | { 73 | return array_shift($this->responses); 74 | } 75 | 76 | /*** 77 | * @inheritdoc 78 | */ 79 | public function getIterator() 80 | { 81 | return new ArrayIterator($this->responses); 82 | } 83 | 84 | /** 85 | * @inheritdoc 86 | */ 87 | public function offsetSet($offset, $value) 88 | { 89 | $this->addResponse($offset, $value); 90 | } 91 | 92 | /** 93 | * @inheritdoc 94 | */ 95 | public function offsetExists($offset) 96 | { 97 | return isset($this->responses[$offset]); 98 | } 99 | 100 | /** 101 | * @inheritdoc 102 | */ 103 | public function offsetUnset($offset) 104 | { 105 | unset($this->responses[$offset]); 106 | } 107 | 108 | /** 109 | * @inheritdoc 110 | */ 111 | public function offsetGet($offset) 112 | { 113 | return isset($this->responses[$offset]) ? $this->responses[$offset] : null; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Centrifugo/Centrifugo.php: -------------------------------------------------------------------------------- 1 | endpoint = $endpoint; 38 | $this->secret = $secret; 39 | $this->client = $client; 40 | } 41 | 42 | /** 43 | * Create request. 44 | * 45 | * @param string $method 46 | * @param array $params 47 | * 48 | * @return Request 49 | */ 50 | public function request($method, array $params = []) 51 | { 52 | return new Request($this->endpoint, $this->secret, $method, $params); 53 | } 54 | 55 | /** 56 | * Send message into channel. 57 | * 58 | * @param string $channel 59 | * @param array $data 60 | * 61 | * @return Response 62 | */ 63 | public function publish($channel, array $data) 64 | { 65 | return $this->sendRequest('publish', ['channel' => $channel, 'data' => $data]); 66 | } 67 | 68 | /** 69 | * Very similar to publish but allows to send the same data into many channels. 70 | * 71 | * @param array $channels 72 | * @param array $data 73 | * 74 | * @return Response 75 | */ 76 | public function broadcast(array $channels, array $data) 77 | { 78 | return $this->sendRequest('broadcast', ['channels' => $channels, 'data' => $data]); 79 | } 80 | 81 | /** 82 | * Unsubscribe user from channel. 83 | * 84 | * @param string $channel 85 | * @param string $userId 86 | * 87 | * @return Response 88 | */ 89 | public function unsubscribe($channel, $userId) 90 | { 91 | return $this->sendRequest('unsubscribe', ['channel' => $channel, 'user' => (string) $userId]); 92 | } 93 | 94 | /** 95 | * Disconnect user by user ID. 96 | * 97 | * @param string $userId 98 | * 99 | * @return Response 100 | */ 101 | public function disconnect($userId) 102 | { 103 | return $this->sendRequest('disconnect', ['user' => (string) $userId]); 104 | } 105 | 106 | /** 107 | * Get channel presence information (all clients currently subscribed on this channel). 108 | * 109 | * @param string $channel 110 | * 111 | * @return Response 112 | */ 113 | public function presence($channel) 114 | { 115 | return $this->sendRequest('presence', ['channel' => $channel]); 116 | } 117 | 118 | /** 119 | * Get channel history information (list of last messages sent into channel). 120 | * 121 | * @param string $channel 122 | * 123 | * @return Response 124 | */ 125 | public function history($channel) 126 | { 127 | return $this->sendRequest('history', ['channel' => $channel]); 128 | } 129 | 130 | /** 131 | * Get channels information (list of currently active channels). 132 | * 133 | * @return Response 134 | */ 135 | public function channels() 136 | { 137 | return $this->sendRequest('channels'); 138 | } 139 | 140 | /** 141 | * Get stats information about running server nodes. 142 | * 143 | * @return Response 144 | */ 145 | public function stats() 146 | { 147 | return $this->sendRequest('stats'); 148 | } 149 | 150 | /** 151 | * Get information about single Centrifugo node. 152 | * 153 | * @param string $endpoint 154 | * 155 | * @return Response 156 | */ 157 | public function node($endpoint) 158 | { 159 | $request = new Request($endpoint, $this->secret, 'node', []); 160 | 161 | return $this->sendBatchRequest([$request]); 162 | } 163 | 164 | /** 165 | * @return Response 166 | */ 167 | public function getLastResponse() 168 | { 169 | return $this->lastResponse; 170 | } 171 | 172 | /** 173 | * Generate client connection token. 174 | * 175 | * @param string $user 176 | * @param string $timestamp 177 | * @param string $info 178 | * 179 | * @return string 180 | */ 181 | public function generateClientToken($user, $timestamp, $info = '') 182 | { 183 | $ctx = hash_init('sha256', HASH_HMAC, $this->secret); 184 | hash_update($ctx, $user); 185 | hash_update($ctx, $timestamp); 186 | hash_update($ctx, $info); 187 | 188 | return hash_final($ctx); 189 | } 190 | 191 | /** 192 | * Generate channel sign. 193 | * 194 | * @param string $client 195 | * @param string $channel 196 | * @param string $info 197 | * 198 | * @return string 199 | */ 200 | public function generateChannelSign($client, $channel, $info = '') 201 | { 202 | $ctx = hash_init('sha256', HASH_HMAC, $this->secret); 203 | hash_update($ctx, $client); 204 | hash_update($ctx, $channel); 205 | hash_update($ctx, $info); 206 | 207 | return hash_final($ctx); 208 | } 209 | 210 | /** 211 | * Send request. 212 | * 213 | * @param string $method 214 | * @param array $params 215 | * 216 | * @return Response 217 | * @throws Exceptions\CentrifugoException 218 | */ 219 | public function sendRequest($method, $params = []) 220 | { 221 | $request = $this->request($method, $params); 222 | $this->lastResponse = $this->client->sendRequest($request); 223 | if ($this->lastResponse->isError()) { 224 | $this->lastResponse->throwException(); 225 | } 226 | 227 | return $this->lastResponse; 228 | } 229 | 230 | /** 231 | * Send batch request. 232 | * 233 | * @param Request[] $requests 234 | * 235 | * @return BatchResponse 236 | */ 237 | public function sendBatchRequest(array $requests) 238 | { 239 | $batchRequest = new BatchRequest($this->endpoint, $this->secret, $requests); 240 | 241 | return $this->lastResponse = $this->client->sendBatchRequest($batchRequest); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/Centrifugo/ClientInterface.php: -------------------------------------------------------------------------------- 1 | successor) { 29 | $this->successor->setFailover($failover); 30 | } else { 31 | $this->successor = $failover; 32 | } 33 | } 34 | 35 | /** 36 | * @param Request $request 37 | * 38 | * @return Response 39 | * @throws Exception 40 | */ 41 | public function sendRequest(Request $request) 42 | { 43 | try { 44 | $rawResponse = $this->processRequest($request); 45 | } catch (Exception $exception) { 46 | if ($this->successor) { 47 | return $this->successor->sendRequest($request); 48 | } 49 | throw $exception; 50 | } 51 | 52 | $response = new Response($request, $rawResponse); 53 | 54 | if ($response->isError()) { 55 | throw $response->getThrownException(); 56 | } 57 | 58 | return $response; 59 | } 60 | 61 | /** 62 | * @param BatchRequest $request 63 | * 64 | * @return BatchResponse 65 | */ 66 | public function sendBatchRequest(BatchRequest $request) 67 | { 68 | $response = $this->sendRequest($request); 69 | 70 | return new BatchResponse($request, $response); 71 | } 72 | 73 | /** 74 | * @param Request $request 75 | * 76 | * @return string 77 | */ 78 | abstract protected function processRequest(Request $request); 79 | } 80 | -------------------------------------------------------------------------------- /src/Centrifugo/Clients/HttpClient.php: -------------------------------------------------------------------------------- 1 | options = $options; 29 | } 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | public function processRequest(Request $request) 35 | { 36 | $connection = curl_init(); 37 | try { 38 | curl_setopt_array($connection, $this->options); 39 | curl_setopt_array($connection, [ 40 | CURLOPT_URL => $request->getEndpoint(), 41 | CURLOPT_HTTPHEADER => $request->getHeaders(), 42 | CURLOPT_POSTFIELDS => $request->getEncodedParams(), 43 | CURLOPT_RETURNTRANSFER => true, 44 | CURLOPT_POST => true, 45 | ]); 46 | 47 | $rawResponse = curl_exec($connection); 48 | 49 | if (curl_errno($connection)) { 50 | throw new CentrifugoTransportException('HttpClient CURL error: ' . curl_error($connection)); 51 | } 52 | 53 | $httpCode = curl_getinfo($connection, CURLINFO_HTTP_CODE); 54 | 55 | if ($httpCode != self::HTTP_CODE_OK) { 56 | throw new CentrifugoTransportException('HttpClient return invalid response code: ' . $httpCode); 57 | } 58 | } finally { 59 | curl_close($connection); 60 | } 61 | 62 | return $rawResponse; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Centrifugo/Clients/Redis/PredisTransport.php: -------------------------------------------------------------------------------- 1 | predisClient = $predisClient; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function rPush($key, $value) 31 | { 32 | return $this->predisClient->rpush($key, [$value]); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Centrifugo/Clients/Redis/RedisTransport.php: -------------------------------------------------------------------------------- 1 | redis = $redis; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function rPush($key, $value) 31 | { 32 | return $this->redis->rpush($key, $value); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Centrifugo/Clients/Redis/TransportInterface.php: -------------------------------------------------------------------------------- 1 | transport = $transport; 40 | } 41 | 42 | /** 43 | * @param int $shardsNumber 44 | */ 45 | public function setShardsNumber($shardsNumber) 46 | { 47 | $this->shardsNumber = (int) $shardsNumber; 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | protected function processRequest(Request $request) 54 | { 55 | if (!$this->canProcessRequest($request)) { 56 | throw new CentrifugoTransportException('RedisTransport can\'t process request.'); 57 | } 58 | 59 | $queue = $this->getQueue(); 60 | $message = $this->makeMassage($request); 61 | 62 | if (false === $this->transport->rPush($queue, $message)) { 63 | throw new CentrifugoTransportException('RedisTransport can\'t push to: ' . $queue); 64 | } 65 | 66 | return $request instanceof BatchRequest 67 | ? $this->emulateBatchResponse($request) 68 | : $this->emulateResponse($request); 69 | } 70 | 71 | /** 72 | * @param Request $request 73 | * 74 | * @return bool 75 | */ 76 | protected function canProcessRequest(Request $request) 77 | { 78 | if ($request instanceof BatchRequest) { 79 | foreach ($request as $req) { 80 | if (!$this->isMethodSupported($req->getMethod())) { 81 | return false; 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | 88 | return $this->isMethodSupported($request->getMethod()); 89 | } 90 | 91 | /** 92 | * @param string $method 93 | * 94 | * @return mixed 95 | */ 96 | protected function isMethodSupported($method) 97 | { 98 | return in_array($method, $this->supportedMethods); 99 | } 100 | 101 | /** 102 | * @param Request $request 103 | * 104 | * @return mixed 105 | */ 106 | protected function makeMassage(Request $request) 107 | { 108 | return json_encode([ 109 | 'data' => $request instanceof BatchRequest ? $request->toArray() : [$request->toArray()], 110 | ]); 111 | } 112 | 113 | /** 114 | * @param Request $request 115 | * 116 | * @return array 117 | */ 118 | protected function emulateResponse(Request $request) 119 | { 120 | return [ 121 | 'body' => $request->getParams(), 122 | 'method' => $request->getMethod(), 123 | 'error' => null, 124 | ]; 125 | } 126 | 127 | /** 128 | * @param BatchRequest $request 129 | * 130 | * @return array 131 | */ 132 | protected function emulateBatchResponse(BatchRequest $request) 133 | { 134 | $response = []; 135 | foreach ($request as $req) { 136 | $response[] = $this->emulateResponse($req); 137 | } 138 | 139 | return $response; 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | protected function getQueue() 146 | { 147 | if (!$this->shardsNumber) { 148 | return self::QUEUE_NAME; 149 | } 150 | 151 | return sprintf(self::QUEUE_SHARD_PATTERN, rand(0, $this->shardsNumber - 1)); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Centrifugo/Exceptions/CentrifugoAlreadySubscribedException.php: -------------------------------------------------------------------------------- 1 | CentrifugoInvalidMessageException::class, 22 | 'invalid token' => CentrifugoInvalidTokenException::class, 23 | 'unauthorized' => CentrifugoUnauthorizedException::class, 24 | 'method not found' => CentrifugoMethodNotFoundException::class, 25 | 'permission denied' => CentrifugoPermissionDeniedException::class, 26 | 'namespace not found' => CentrifugoNamespaceNotFoundException::class, 27 | 'internal server error' => CentrifugoInternalServerErrorException::class, 28 | 'already subscribed' => CentrifugoAlreadySubscribedException::class, 29 | 'limit exceeded' => CentrifugoLimitExceededException::class, 30 | 'not available' => CentrifugoNotAvailableException::class, 31 | 'send timeout' => CentrifugoSendTimeoutException::class, 32 | 'client is closed' => CentrifugoClientClosedException::class, 33 | ]; 34 | 35 | /** 36 | * CentrifugoResponseException constructor. 37 | * 38 | * @param Response $response 39 | * @param CentrifugoException|null $previousException 40 | */ 41 | public function __construct(Response $response, CentrifugoException $previousException = null) 42 | { 43 | $this->response = $response; 44 | 45 | parent::__construct('Invalid Centrifugo response.', 0, $previousException); 46 | } 47 | 48 | /** 49 | * @return Response 50 | */ 51 | public function getResponse() 52 | { 53 | return $this->response; 54 | } 55 | 56 | /** 57 | * @param Response $response 58 | * 59 | * @return static 60 | */ 61 | public static function create(Response $response) 62 | { 63 | $errorMessage = $response->getError(); 64 | $exception = isset(static::$exceptionMap[$errorMessage]) 65 | ? static::$exceptionMap[$errorMessage] 66 | : CentrifugoException::class; 67 | 68 | return new static($response, new $exception($errorMessage)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Centrifugo/Exceptions/CentrifugoSendTimeoutException.php: -------------------------------------------------------------------------------- 1 | endpoint = $endpoint; 39 | $this->secret = $secret; 40 | $this->method = $method; 41 | $this->params = $params; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getMethod() 48 | { 49 | return $this->method; 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function getParams() 56 | { 57 | return $this->params; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getEndpoint() 64 | { 65 | return $this->endpoint; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getEncodedParams() 72 | { 73 | return json_encode($this->toArray()); 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | public function getHeaders() 80 | { 81 | return [ 82 | 'Content-Type: application/json', 83 | 'X-API-Sign: ' . $this->generateHashSign(), 84 | ]; 85 | } 86 | 87 | /** 88 | * @return array 89 | */ 90 | public function toArray() 91 | { 92 | return ['method' => $this->method, 'params' => $this->params]; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | protected function generateHashSign() 99 | { 100 | $ctx = hash_init('sha256', HASH_HMAC, $this->secret); 101 | hash_update($ctx, $this->getEncodedParams()); 102 | 103 | return hash_final($ctx); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Centrifugo/Response.php: -------------------------------------------------------------------------------- 1 | request = $request; 50 | $this->body = $body; 51 | $this->error = $error; 52 | $this->method = $method; 53 | $this->decodeBody(); 54 | } 55 | 56 | /** 57 | * @return Request 58 | */ 59 | public function getRequest() 60 | { 61 | return $this->request; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getBody() 68 | { 69 | return $this->body; 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | public function getDecodedBody() 76 | { 77 | return $this->decodedBody; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getError() 84 | { 85 | return $this->error; 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getMethod() 92 | { 93 | return $this->method; 94 | } 95 | 96 | /** 97 | * @return boolean 98 | */ 99 | public function isError() 100 | { 101 | return isset($this->error); 102 | } 103 | 104 | /** 105 | * Throws the exception. 106 | * 107 | * @throws CentrifugoException 108 | */ 109 | public function throwException() 110 | { 111 | throw $this->thrownException; 112 | } 113 | 114 | /** 115 | * Instantiates an exception to be thrown later. 116 | */ 117 | public function makeException() 118 | { 119 | $this->thrownException = CentrifugoResponseException::create($this); 120 | } 121 | 122 | /** 123 | * @return CentrifugoException 124 | */ 125 | public function getThrownException() 126 | { 127 | return $this->thrownException; 128 | } 129 | 130 | /** 131 | * Decode body 132 | */ 133 | public function decodeBody() 134 | { 135 | $this->decodedBody = is_array($this->body) ? $this->body : json_decode($this->body, JSON_OBJECT_AS_ARRAY); 136 | if ($this->isError()) { 137 | $this->makeException(); 138 | } 139 | } 140 | } 141 | --------------------------------------------------------------------------------