Skip to content

Commit 27bda19

Browse files
Namoshekthg2k
andauthored
Refactoring and overhaul of the package in preparation of v1 (#51)
* Add more documentation * Use QoS constant in ConnectionSettings * Add isConnected() method to MQTTClient (contract) * Ensure client is connected before performing actions * Use QoS constants in favour of integers * Document hooks in MQTTClient contract * Improve PhpDoc of the interrupt() method * Remove unused shift($buffer, $limit) method * Extract pure functions into traits * Replace local message id with a manager implementation This change allows us to guarantee that message ids are never used twice at the same time. It also allows for different implementations, e.g. with persistence. * Add missing connection handshake errors of v3.1 * Merge MessageIdManager into Repository * Refactor ConnectionSettings The connection settings do now provide a fluent setter interface instead of a large constructor. Besides this change, the configurable caFile has been moved from the MQTTClient class to the connection settings. A bunch of new TLS settings have been added as well. * Avoid passing null for cafile/capath to context * Improve logging All logging statements have been reworked to utilize the log context interpolation provided by the PSR-3 log standard. Some log statements have been reduced in severity (from info to debug). Most log statements now contain the broker (referenced by host and port). Co-Authored-By: Giovanni Giacobbi <[email protected]> * Add and use log helpers New log helper methods are used to add common information and context to log messages. All log messages are now prepended with broker information (host and port) as well as the client id. Also the word "MQTT" is now part of the common log output, making it obsolete for individual messages. Co-Authored-By: Giovanni Giacobbi <[email protected]> * Wrap calling registered callbacks in try-catch Whenever registered callbacks are called, we catch all exceptions thrown by user provided code to avoid crashes of the client. We still log such exceptions as errors though. Co-Authored-By: Giovanni Giacobbi <[email protected]> * Rename MQTTClient to MqttClient * Always create a socket context We also create a socket context without any settings if none are configured. This prevents accidental socket context pollution by other parts of an application. * Rename remaining occurences of MQTTClient* to MqttClient* (#18) * Refactor MqttClient protocol logic into MessageProcessor implementation (#28) * Implement basic MQTT v3 message processor The current implementation does parse messages, but it does not handle them just yet. * Refactor more functionality into message processor * Log sending/receiving data * Implement new loop logic * Split message parsing and processing * Throw more correct exceptions * Fix required bytes calculation * Improve data readin / connection logic * Fix wrong change in readFromSocket() * Add phpunit/phpunit as dev dependency * Only pass clientId to message processor * Add initial test for message processor and fix issue * Fix typo * Wait for first byte in connection logic Doing so avoids polling over and over again. Since we know, there must be at least one byte in the response. * Add an abstract BaseMessageProcessor * Fix protocol version in message processor docs * Use string to represent protocol version Since there are different protocol versions like 3, 3.1, 3.1.1 of which some use the same protocol level (like 3 and 3.1, which both use `3` as protocol level), it makes more sense to represent the protocol version as string. This also makes it less error prone if more protocol version levels are added. * Make ConnectionSettings immutable Having immutable connection settings prevents the settings from being altered after passing them to the connect() method. This is important since some settings are only used during the life time of a session. And if auto-reconnect is being implemented at some point, using the same settings for the reconnect will be essential as well. * Fix double use of $ * Restructure and rename connection settings The $qualityOfService and $retain settings are actually intended for the last will. We now transport this information through the name. * Restructure ConnectionSetting properties again * Update README.md The latest documentation improvements of the master branch have been added, but updated for the refactoring branch details. * Remove the option use blocking mode for the socket Since we decide based on the protocol logic whether to block for data on a per-call-basis or not, it is not necessary to set a global blocking mode on the socket itself. * Add connection settings validation * Fix phpcs * Add constructors to exceptions * Implement TLS updates of master * Validate subscription before calling unsubscribe() * Remove obsolete TODO It is not necessary to validate the message id anymore, since the message id is now guaranteed to be unique. * Add client certificate support for TLS * Fix issue when not passing any connection settings * Make stream non-blocking A blocking stream causes connection abortions, and we don't want that. * Add safety break when decoding message length * Fix message length encoding This fix has been taken from the master branch, see #30. * Add composer scripts for tests * Add getters for received and sent bytes * Add unit test for buildConnectMessage() * Add unit test for buildSubscribeMessage() * Add unit test for buildPingMessage() and buildDisconnectMessage() * Add remaining unit tests for message processor * Implement draft of RedisRepository * Subscribers of lower QoS receive higher QoS too In case multiple subscriptions of different topics matching the topic of a published message with different QoS exist, we also deliver higher QoS messages to subscribers with a lower QoS. Actually, the QoS doesn't matter at all in this case, since the broker handles it accordingly. * Run test and code style pipeline on push and PR * Fix code style issues * Inline 'addNew(...)' methods to MqttClient to reduce Repository interface (#35) * Add missing PUBREL in response to PUBREC * Use camelCase for logging context * Removed 'BaseRepository' class as it's covered by the 'Repository' interface (#36) * Fix message format of PUBREL since we expect ack * Refactory repository interface with single pending message and subscriptions management (#33) * Refactored Repository interface and pending message handling. Details: * Refactored the Repository interface. The new interface stores a generic serializable PendingMessage without knowing which type it is, using two different queues one for incoming and one for outgoing messages. There are currently three types of outgoing messages (PublishedMessage, SubscribeRequest, UnsubscribeRequest) and one incoming message (PublishedMessaged) handled. * The `TopicSubscription` class has been renamed to `Subscription` and now represents only the subscription itself, separating it from the concept of SubscribeRequest which might include several topic filters in one message. * Removed exception `UnexpectedAcknowledgementException`. It is expected to have duplicate packets over the wire, so there is no need for an expection in this case, a notice to the logger is enough. * Added `ProtocolViolationException`. This explicitly marks situations where the broker is misbehaving and the connection should be terminated. * Fixed some problems with the MessageProcessor. * Removed Redis repository implementation until updated to the new Repository interface * Fix MessageProcessor, exceptions and documentation * Change visibility and description of DTOs * Simplify acknowledgement parsing logic * Change code formatting * Change field visibility and formatting Co-authored-by: Marvin Mall <[email protected]> * Update from master * Fix composer.json * Change how CI runs tests * Fix security vulnerability * Refactor the loop() method to reduce complexity Co-authored-by: Giovanni Giacobbi <[email protected]> Co-authored-by: Giovanni Giacobbi <[email protected]>
1 parent 289fa18 commit 27bda19

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+4982
-2879
lines changed

.github/workflows/tests.yml

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
name: Tests
22

3-
on: [push]
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
types: [opened, synchronize, reopened]
49

510
jobs:
611
test-all:
12+
name: Test PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }}
13+
714
runs-on: ${{ matrix.operating-system }}
815

916
strategy:
1017
matrix:
1118
operating-system: [ubuntu-latest]
12-
php-versions: [7.2, 7.3, 7.4]
13-
14-
name: PHP ${{ matrix.php-versions }} tests on ${{ matrix.operating-system }}
19+
php-version: ['7.2', '7.3', '7.4']
1520

1621
steps:
1722
- uses: actions/checkout@v2
1823

1924
- uses: shivammathur/setup-php@v2
2025
with:
21-
php-version: ${{ matrix.php-versions }}
26+
php-version: ${{ matrix.php-version }}
2227
tools: phpunit:8.5.8
2328
coverage: pcov
2429

@@ -47,4 +52,4 @@ jobs:
4752
uses: Namoshek/[email protected]
4853

4954
- name: Run phpunit tests
50-
run: phpunit
55+
run: composer test

.github/workflows/verify-code-style.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
name: 'PR: Verify Code Style'
1+
name: 'Verify Code Style'
22

3-
on: pull_request
3+
on: [push, pull_request]
44

55
jobs:
66
phpcs:

.phpcs.xml

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
<rule ref="Zend.Files.ClosingTag"/>
8282

8383
<file>src</file>
84-
<file>tests</file>
8584

8685
<arg name="colors"/>
8786
<arg value="sp"/>

README.md

+46-33
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# php-mqtt/client
22

3-
[![Latest Stable Version](https://poser.pugx.org/php-mqtt/client/v)](//packagist.org/packages/php-mqtt/client)
4-
[![Total Downloads](https://poser.pugx.org/php-mqtt/client/downloads)](//packagist.org/packages/php-mqtt/client)
5-
![Tests](https://github.com/php-mqtt/client/workflows/Tests/badge.svg)
6-
[![License](https://poser.pugx.org/php-mqtt/client/license)](//packagist.org/packages/php-mqtt/client)
3+
[![Latest Stable Version](https://poser.pugx.org/php-mqtt/client/v)](https://packagist.org/packages/php-mqtt/client)
4+
[![Total Downloads](https://poser.pugx.org/php-mqtt/client/downloads)](https://packagist.org/packages/php-mqtt/client)
5+
[![Tests](https://github.com/php-mqtt/client/workflows/Tests/badge.svg)](https://github.com/php-mqtt/client/actions?query=workflow%3ATests)
6+
[![License](https://poser.pugx.org/php-mqtt/client/license)](https://packagist.org/packages/php-mqtt/client)
77

8-
[`php-mqtt/client`](https://packagist.org/packages/php-mqtt/client) was created by, and is maintained by [Namoshek](https://github.com/namoshek).
8+
[`php-mqtt/client`](https://packagist.org/packages/php-mqtt/client) was created by, and is maintained
9+
by [Namoshek](https://github.com/namoshek).
910
It allows you to connect to an MQTT broker where you can publish messages and subscribe to topics.
10-
The implementation supports all QoS levels ([with limitations](#limitations)).
11+
The current implementation supports all QoS levels ([with limitations](#limitations)).
1112

1213
## Installation
1314

@@ -28,7 +29,7 @@ $server = 'some-broker.example.com';
2829
$port = 1883;
2930
$clientId = 'test-publisher';
3031

31-
$mqtt = new \PhpMqtt\Client\MQTTClient($server, $port, $clientId);
32+
$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
3233
$mqtt->connect();
3334
$mqtt->publish('php-mqtt/client/test', 'Hello World!', 0);
3435
$mqtt->close();
@@ -45,7 +46,7 @@ Subscribing is a little more complex than publishing as it requires to run an ev
4546
```php
4647
$clientId = 'test-subscriber';
4748

48-
$mqtt = new \PhpMqtt\Client\MQTTClient($server, $port, $clientId);
49+
$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
4950
$mqtt->connect();
5051
$mqtt->subscribe('php-mqtt/client/test', function ($topic, $message) {
5152
echo sprintf("Received message on topic [%s]: %s\n", $topic, $message);
@@ -61,7 +62,7 @@ pcntl_async_signals(true);
6162

6263
$clientId = 'test-subscriber';
6364

64-
$mqtt = new \PhpMqtt\Client\MQTTClient($server, $port, $clientId);
65+
$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
6566
pcntl_signal(SIGINT, function (int $signal, $info) use ($mqtt) {
6667
$mqtt->interrupt();
6768
});
@@ -75,8 +76,9 @@ $mqtt->close();
7576

7677
### Client Settings
7778

78-
As shown in the examples above, the `MQTTClient` takes the server, port and client id as first, second and third parameter.
79-
As fourth parameter, the path to a CA file can be passed which will enable TLS and is used to verify the peer.
79+
As shown in the examples above, the `MqttClient` takes the server, port and client id as first, second and third parameter.
80+
As fourth parameter, the protocol level can be passed. Currently supported is MQTT v3.1,
81+
available as constant `MqttClient::MQTT_3_1`.
8082
A fifth parameter allows passing a repository (currently, only a `MemoryRepository` is available by default).
8183
Lastly, a logger can be passed as sixth parameter. If none is given, a null logger is used instead.
8284

@@ -86,45 +88,56 @@ $mqtt = new \PhpMqtt\Client\MQTTClient(
8688
$server,
8789
$port,
8890
$clientId,
89-
'/path/to/ca/file',
91+
\PhpMqtt\Client\MqttClient::MQTT_3_1,
9092
new \PhpMqtt\Client\Repositories\MemoryRepository(),
9193
new Logger()
9294
);
9395
```
9496

95-
The logger must implement the `Psr\Log\LoggerInterface`.
97+
The `Logger` must implement the `Psr\Log\LoggerInterface`.
9698

9799
### Connection Settings
98100

99-
The `connect()` method of the `MQTTClient` takes four optional parameters:
100-
1. Username
101-
2. Password
102-
3. A `ConnectionSettings` instance
103-
4. A `boolean` flag indicating whether a clean session should be requested (a random client id does this implicitly)
101+
The `connect()` method of the `MQTTClient` takes two optional parameters:
102+
1. A `ConnectionSettings` instance
103+
2. A `boolean` flag indicating whether a clean session should be requested (a random client id does this implicitly)
104104

105105
Example:
106106
```php
107107
$mqtt = new \PhpMqtt\Client\MQTTClient($server, $port, $clientId);
108108

109109
$connectionSettings = new \PhpMqtt\Client\ConnectionSettings();
110-
$mqtt->connect($username, $password, $connectionSettings, true);
110+
$mqtt->connect($connectionSettings, true);
111111
```
112112

113-
The `ConnectionSettings` class has the following constructor and defaults:
113+
The `ConnectionSettings` class provides a few settings through a fluent interface. The type itself is immutable,
114+
and a new `ConnectionSettings` instance will be created for each added option.
115+
This also prevents changes to the connection settings after a connection has been established.
116+
117+
The following is a complete list of options with their respective default:
114118
```php
115-
public function __construct(
116-
int $qualityOfService = 0,
117-
bool $retain = false,
118-
bool $blockSocket = false,
119-
int $socketTimeout = 5,
120-
int $keepAlive = 10,
121-
int $resendTimeout = 10,
122-
string $lastWillTopic = null,
123-
string $lastWillMessage = null,
124-
bool $useTls = false,
125-
bool $tlsVerifyPeer = true,
126-
bool $tlsVerifyName = true
127-
) { ... }
119+
$connectionSettings = (new \PhpMqtt\Client\ConnectionSettings())
120+
121+
// The QoS level
122+
->setUsername(null)
123+
->setPassword(null)
124+
->setConnectTimeout(60)
125+
->setSocketTimeout(5)
126+
->setKeepAliveInterval(10)
127+
->setResendTimeout(10)
128+
->setLastWillTopic(null)
129+
->setLastWillMessage(null)
130+
->setLastWillQualityOfService(0)
131+
->setRetainLastWill(false)
132+
->setUseTls(false)
133+
->setTlsVerifyPeer(true)
134+
->setTlsVerifyPeerName(true)
135+
->setTlsSelfSignedAllowed(false)
136+
->setTlsCertificateAuthorityFile(null)
137+
->setTlsCertificateAuthorityPath(null)
138+
->setTlsClientCertificateFile(null)
139+
->setTlsClientCertificateKeyFile(null)
140+
->setTlsClientCertificatePassphrase(null);
128141
```
129142

130143
## Features

composer.json

+22-5
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,34 @@
2121
"PhpMqtt\\Client\\": "src"
2222
}
2323
},
24+
"autoload-dev": {
25+
"psr-4": {
26+
"Tests\\": "tests/"
27+
}
28+
},
2429
"require": {
2530
"php": "^7.2",
26-
"psr/log": "^1.1"
31+
"psr/log": "^1.1",
32+
"myclabs/php-enum": "^1.7",
33+
"opis/closure": "^3.5"
2734
},
2835
"require-dev": {
36+
"phpunit/php-invoker": "^3.0",
2937
"phpunit/phpunit": "^8.0",
30-
"squizlabs/php_codesniffer": "^3.5",
31-
"phpunit/php-invoker": "^3.0"
38+
"squizlabs/php_codesniffer": "^3.5"
39+
},
40+
"suggest": {
41+
"ext-redis": "Required for the RedisRepository"
3242
},
3343
"scripts": {
34-
"test:cs": "vendor/bin/phpcs src tests",
35-
"fix:cs": "vendor/bin/phpcbf src tests"
44+
"fix:cs": "vendor/bin/phpcbf",
45+
"test": [
46+
"@test:cs",
47+
"@test:unit",
48+
"@test:feature"
49+
],
50+
"test:cs": "vendor/bin/phpcs",
51+
"test:feature": "vendor/bin/phpunit --testdox --testsuite Feature",
52+
"test:unit": "vendor/bin/phpunit --testdox --testsuite Unit"
3653
}
3754
}

0 commit comments

Comments
 (0)