PHPackages                             chipslays/porter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. chipslays/porter

ActiveLibrary[HTTP &amp; Networking](/categories/http)

chipslays/porter
================

1.2.9(3y ago)285MITPHPPHP ^8.1CI passing

Since Mar 23Pushed 1y ago1 watchersCompare

[ Source](https://github.com/chipslays/porter)[ Packagist](https://packagist.org/packages/chipslays/porter)[ RSS](/packages/chipslays-porter/feed)WikiDiscussions master Synced today

READMEChangelog (10)Dependencies (6)Versions (112)Used By (0)

Porter 🤵‍
=========

[](#porter-‍)

[![](https://camo.githubusercontent.com/33c6204c675e08834c23bd4a6ebb77511b8aa91fe3509f3839c4706375964f90/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f63686970736c6179732f706f727465723f636f6c6f723d626c7565)](https://camo.githubusercontent.com/33c6204c675e08834c23bd4a6ebb77511b8aa91fe3509f3839c4706375964f90/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f63686970736c6179732f706f727465723f636f6c6f723d626c7565)[![](https://camo.githubusercontent.com/90bb9a96f041852ead250db94c017537a26736b4962c27f0e662145ddb962b02/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f63686970736c6179732f706f72746572)](https://camo.githubusercontent.com/90bb9a96f041852ead250db94c017537a26736b4962c27f0e662145ddb962b02/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f63686970736c6179732f706f72746572)

A simple PHP 8 websocket server and client wrapper over Workerman with events, channels and other stuff, can say this is a Socket IO alternative for PHP.

[![](/.github/images/porter.png)](/.github/images/porter.png)

> **Note**
>
> Latest version 1.2 is production ready, maintenance only small features, fix bugs and has **no breaking changes** updates.

🧰 Installation
==============

[](#-installation)

1. Install Porter via Composer:

```
composer require chipslays/porter
```

2. Put javascript code in views:

```

```

3. All done.

> Laravel integration can be found [here](/examples/laravel/README.md).

👨‍💻 Usage
=========

[](#‍-usage)

### Server (PHP)

[](#server-php)

Simplest ping-pong server.

```
use Porter\Events\Event;

require __DIR__ . '/vendor/autoload.php';

server()->create('0.0.0.0:3737');

server()->on('ping', function (Event $event) {
    $event->reply('pong');
});

server()->start();
```

Run server.

```
php server.php start
```

Or run server in background as daemon process.

```
php server.php start -d
```

 List of all available commands`php server.php start`

`php server.php start -d`

`php server.php status`

`php server.php status -d`

`php server.php connections`

`php server.php stop`

`php server.php stop -g`

`php server.php restart`

`php server.php reload`

`php server.php reload -g`

### Client (Javascript)

[](#client-javascript)

Send `ping` event on established connection.

```

    const client = new Porter(`ws://${location.hostname}:3737`);

    client.connected = () => {
        client.send('ping');
    }

    client.on('pong', payload => {
        console.log(payload);
    });

    client.listen();

```

💡 Examples
==========

[](#-examples)

Examples can be found [here](/examples).

📚 Documentation
===============

[](#-documentation)

> **NOTE:** The documentation may not contain the latest updates or may be out of date in places. See examples, code and comments on methods. The code is well documented.

Basics
------

[](#basics)

### Local development

[](#local-development)

```
use Workerman\Worker;

$worker = new Worker('websocket://0.0.0.0:3737');
```

### On server with SSL

[](#on-server-with-ssl)

```
use Workerman\Worker;

$context = [
    // More see http://php.net/manual/en/context.ssl.php
    'ssl' => [
        'local_cert' => '/path/to/cert.pem',
        'local_pk' => '/path/to/privkey.pem',
        'verify_peer' => false,
        // 'allow_self_signed' => true,
    ],
];
$worker = new Worker('websocket://0.0.0.0:3737', $context);
$worker->transport = 'ssl';
```

🔹 `Server`
----------

[](#-server)

Can be used anywhere as function `server()` or `Server::getInstance()`.

```
use Porter\Server;

$server = Server::getInstance();
$server->on(...);
```

```
server()->on(...);
```

#### `boot(Worker $worker): self`

[](#bootworker-worker-self)

Booting websocket server. It method init all needle classes inside.

> Use this method instead of constructor.

```
$server = Server::getInstance();
$server->boot($worker);

// by helper function
server()->boot($worker);
```

#### `setWorker(Worker $worker): void`

[](#setworkerworker-worker-void)

Set worker instance.

```
use Workerman\Worker;

$worker = new Worker('websocket://0.0.0.0:3737');
server()->boot($worker); // booting server

$worker = new Worker('websocket://0.0.0.0:3737');
$worker->... // configure new worker

// change worker in already booted server
server()->setWorker($worker);
```

#### `setWorker(Worker $worker): void`

[](#setworkerworker-worker-void-1)

Set worker instance.

```
use Workerman\Worker;

$worker = new Worker('websocket://0.0.0.0:3737');
server()->setWorker($worker);
```

#### `getWorker(): Worker`

[](#getworker-worker)

Get worker instance.

```
server()->getWorker();
```

#### `addEvent(AbstractEvent|string $event): self`

[](#addeventabstracteventstring-event-self)

Add event class handler.

```
use Porter\Server;
use Porter\Payload;
use Porter\Connection;
use Porter\Events\AbstractEvent;

class Ping extends AbstractEvent
{
    public static string $id = 'ping';

    public function handle(Connection $connection, Payload $payload, Server $server): void
    {
        $this->reply('pong');
    }
}

server()->addEvent(Ping::class);
```

#### `autoloadEvents(string $path, string|array $masks = ['*.php', '**/*.php']): void`

[](#autoloadeventsstring-path-stringarray-masks--php-php-void)

Autoload all events inside passed path.

> Note: Use it instead manual add events by `addEvent` method.

```
server()->autoloadEvents(__DIR__ . '/Events');
```

#### `on(string $type, callable $handler): void`

[](#onstring-type-callable-handler-void)

> **Note**
>
> `Event $event` class extends and have all methods &amp; properties of `AbstractEvent`.

```
$server->on('ping', function (Event $event) {
    $event->reply('pong');
});
```

#### `start(): void`

[](#start-void)

Start server.

```
server()->start();
```

#### `onConnected(callable $handler): void`

[](#onconnectedcallable-handler-void)

Emitted when a socket connection is successfully established.

> In this method available vars: `$_GET`, `$_COOKIE`, `$_SERVER`.

```
use Porter\Terminal;
use Porter\Connection;

server()->onConnected(function (Connection $connection, string $header) {
    Terminal::print('{text:darkGreen}Connected: ' . $connection->getRemoteAddress());

    // Here also available vars: $_GET, $_COOKIE, $_SERVER.
    Terminal::print("Query from client: {text:darkYellow}foo={$_GET['foo']}");
});
```

#### `onDisconnected(callable $handler): void`

[](#ondisconnectedcallable-handler-void)

Emitted when the other end of the socket sends a FIN packet.

> **NOTICE:** On disconnect client connection will leave of all the channels where he was.

```
use Porter\Terminal;
use Porter\Connection;

server()->onDisconnected(function (Connection $connection) {
    Terminal::print('{text:darkGreen}Connected: ' . $connection->getRemoteAddress());
});
```

#### `onError(callable $handler): void`

[](#onerrorcallable-handler-void)

Emitted when an error occurs with connection.

```
use Porter\Terminal;
use Porter\Connection;

server()->onError(function (Connection $connection, $code, $message) {
    Terminal::print("{bg:red}{text:white}Error {$code} {$message}");
});
```

#### `onStart(callable $handler): void`

[](#onstartcallable-handler-void)

Emitted when worker processes start.

```
use Porter\Terminal;
use Workerman\Worker;

server()->onStart(function (Worker $worker) {
    //
});
```

#### `onStop(callable $handler): void`

[](#onstopcallable-handler-void)

Emitted when worker processes stoped.

```
use Porter\Terminal;
use Workerman\Worker;

server()->onStop(function (Worker $worker) {
    //
});
```

#### `onReload(callable $handler): void`

[](#onreloadcallable-handler-void)

Emitted when worker processes get reload signal.

```
use Porter\Terminal;
use Workerman\Worker;

server()->onReload(function (Worker $worker) {
    //
});
```

#### `onRaw(callable $handler): void`

[](#onrawcallable-handler-void)

Handle non event messages (raw data).

```
server()->onRaw(function (string $payload, Connection $connection) {
    if ($payload == 'ping') {
        $connection->send('pong');
    }
});
```

#### `to(TcpConnection|Connection|array $connection, string $event, array $data = []): self`

[](#totcpconnectionconnectionarray-connection-string-event-array-data---self)

Send event to connection.

```
server()->to($connection, 'ping');
```

#### `broadcast(string $event, array $data = [], array $excepts = []): void`

[](#broadcaststring-event-array-data---array-excepts---void)

Send event to all connections.

Yes, to **all connections** on server.

```
server()->broadcast('chat message', [
    'nickname' => 'John Doe',
    'message' => 'Hello World!',
]);
```

#### `storage(): Storage`

[](#storage-storage)

Getter for Storage class.

```
server()->storage();

server()->storage()->put('foo', 'bar');

$storage = server()->storage();
$storage->get('foo');

// can also be a get as propperty
server()->storage()->put('foo', 'bar');
$storage = server()->storage;
```

#### `channels(): Channels`

[](#channels-channels)

Getter for Channels class.

```
server()->channels();

server()->channels()->create('secret channel');

$channels = server()->channels();
$channels->get('secret channel');
```

#### `connection(int $connectionId): ?Connection`

[](#connectionint-connectionid-connection)

Get connection instance by id.

```
$connection = server()->connection(1);
server()->to($connection, 'welcome message', [
    'text' => 'Hello world!'
]);

// also can get like
$connection = server()->getWorker()->connections[1337] ?? null;
```

#### `connections(): Collection[]`

[](#connections-collection)

Get collection of all connections on server.

```
$connections = server()->connections();
server()->broadcast('update users count', ['count' => $connections->count()]);

// also can get like
$connections = server()->getWorker()->connections;
```

#### `validator(): Validator`

[](#validator-validator)

Create validator instance.

See [documenation &amp; examples](https://respect-validation.readthedocs.io/en/latest) how to use.

```
$v = server()->validator();

if ($v->email()->validate('john.doe@example.com')) {
    //
}

// available as helper
if (validator()->contains('example.com')->validate('john.doe@example.com')) {
    //
}
```

🔹 `Channels`
------------

[](#-channels)

This is a convenient division of connected connections into channels.

One connection can consist of an unlimited number of channels.

Channels also support broadcasting and their own storage.

Channel can be access like:

```
// by method
server()->channels();

// by property
server()->channels;
```

#### `create(string $id, array $data = []): Channel`

[](#createstring-id-array-data---channel)

Create new channel.

```
$channel = server()->channels()->create('secret channel', [
    'foo' => 'bar',
]);

$channel->join($connection)->broadcast('welcome message', [
    'foo' => $channel->data->get('foo'),
]);
```

#### `get(string $id): ?Channel`

[](#getstring-id-channel)

Get a channel.

> Returns `NULL` if channel not exists.

```
$channel = server()->channels()->get('secret channel');
```

#### `all(): Channel[]`

[](#all-channel)

Get array of channels (`Channel` instances).

```
foreach (server()->channels()->all() as $id => $channel) {
    echo $channel->connections()->count() . ' connection(s) in channel: ' . $id . PHP_EOL;
}
```

#### `count(): int`

[](#count-int)

Get count of channels.

```
$count = server()->channels()->count();

echo "Total channels: {$count}";
```

#### `delete(string $id): void`

[](#deletestring-id-void)

Delete channel.

```
server()->channels()->delete('secret channel');
```

#### `exists(string $id): bool`

[](#existsstring-id-bool)

Checks if given channel id exists already.

```
$channelId = 'secret channel';
if (!server()->channels()->exists($channelId)) {
    server()->channels()->create($channelId);
}
```

#### `join(string $id, Connection|Connection[]|int[] $connections): Channel`

[](#joinstring-id-connectionconnectionint-connections-channel)

Join or create and join to channel.

```
server()->channels()->join($connection);
server()->channels()->join([$connection1, $connection2, $connection3, ...]);
```

🔹 `Channel`
-----------

[](#-channel)

#### `join(TcpConnection|Connection|array $connections): self`

[](#jointcpconnectionconnectionarray-connections-self)

Join given connections to channel.

```
$channel = server()->channel('secret channel');
$channel->join($connection);
$channel->join([$connection1, $connection2, $connection3, ...]);
```

#### `leave(TcpConnection|Connection $connection): self`

[](#leavetcpconnectionconnection-connection-self)

Remove given connection from channel.

```
$channel = server()->channel('secret channel');
$channel->leave($connection);
```

#### `exists(TcpConnection|Connection|int $connection): bool`

[](#existstcpconnectionconnectionint-connection-bool)

Checks if given connection exists in channel.

```
$channel = server()->channel('secret channel');
$channel->exists($connection);
```

#### `connections(): Connections`

[](#connections-connections)

A array of connections in this channel. Key is a `id` of connection, and value is a instance of connection `Connection`.

```
$channel = server()->channel('secret channel');

foreach($channel->connections()->all()) as $connection) {
    $connection->lastMessageAt = time();
}
```

```
$channel = server()->channel('secret channel');

$connection = $channel->connections()->get([1337]); // get connection with 1337 id
```

#### `broadcast(string $event, array $data = [], array $excepts = []): void`

[](#broadcaststring-event-array-data---array-excepts---void-1)

Send an event to all connection on this channel.

> `TcpConnection[]|Connection[]|int[] $excepts` Connection instance or connection id.

```
$channel = server()->channel('secret channel');
$channel->broadcast('welcome message', [
    'text' => 'Hello world',
]);
```

For example, you need to send to all participants in the room except yourself, or other connections.

```
$channel->broadcast('welcome message', [
    'text' => 'Hello world',
], [$connection]);

$channel->broadcast('welcome message', [
    'text' => 'Hello world',
], [$connection1, $connection2, ...]);
```

#### `destroy(): void`

[](#destroy-void)

Delete this channel from channels.

```
$channel = server()->channel('secret channel');
$channel->destroy();

// now if use $channel, you get an error
$channel->data->get('foo');
```

### Lifehack for `Channel`

[](#lifehack-for-channel)

You can add channel to current user as property to `$connection` instance and get it anywhere.

```
$channel = channel('secret channel');
$connection->channel = &$channel;
```

Properties
----------

[](#properties)

#### `$channel->data`

[](#channel-data)

Data is a simple implement of box for storage your data.

Data is a object of powerful [chipslays/collection](https://github.com/chipslays/collection).

See [documentation](https://github.com/chipslays/collection) for more information how to manipulate this data.

> **NOTICE:** All this data will be deleted when the server is restarted.

Two of simple-short examples:

```
$channel->data->set('foo');
$channel->data->get('foo', 'default value');
$channel->data->has('foo', 'default value');

$channel->data['foo'];
$channel->data['foo'] ?? 'default value';
isset($channel->data['foo']);

// see more examples here: https://github.com/chipslays/collection
```

🔹 `Payload`
-----------

[](#-payload)

The payload is the object that came from the client.

#### `payload(string $key, mixed $default = null): mixed`

[](#payloadstring-key-mixed-default--null-mixed)

Get value from data.

```
$payload->get('foo', 'default value');

// can also use like:
$payload->data->get('foo', 'default value');
$payload->data['foo'] ?? 'default value';
```

#### `is(string|array $rule, string $key): bool`

[](#isstringarray-rule-string-key-bool)

Validate payload data.

See [documenation &amp; examples](https://respect-validation.readthedocs.io/en/latest) how to use.

```
$payload->is('StringType', 'username'); // return true if username is string
$payload->is(['contains', 'john'], 'username'); // return true if $payload->data['username'] contains 'john'
```

Properties
----------

[](#properties-1)

#### `$payload->type`

[](#payload-type)

Is a id of event, for example, `welcome message`.

```
$payload->type; // string
```

#### `$payload->data`

[](#payload-data)

An object of values passed from the client.

Object of [chipslays/collection](https://github.com/chipslays/collection).

See [documentation](https://github.com/chipslays/collection) for more information how to manipulate this data.

```
$payload->data; // Collection

$payload->data->set('foo');
$payload->data->get('foo', 'default value');
$payload->data->has('foo', 'default value');

$payload->data['foo'];
$payload->data['foo'] ?? 'default value';
isset($payload->data['foo']);

// see more examples here: https://github.com/chipslays/collection
```

#### `$payload->rules` \[protected\]

[](#payload-rules-protected)

Auto validate payload data on incoming event.

Available only in events as `class`.

```
use Porter\Server;
use Porter\Payload;
use Porter\Connection;
use Porter\Events\AbstractEvent;

return new class extends AbstractEvent
{
    public static string $type = 'hello to';

    protected array $rules = [
        'username' => ['stringType', ['length', [3, 18]]],
    ];

    public function handle(Connection $connection, Payload $payload, Server $server): void
    {
        if (!$this->validate()) {
            $this->reply('bad request', ['errors' => $this->errors]);
            return;
        }

        $username = $this->payload->data['username'];
        $this->reply(data: ['message' => "Hello, {$username}!"]);
    }
};
```

🔹 `Events`
----------

[](#-events)

Events can be as a separate class or as an anonymous function.

### Event class

[](#event-class)

Basic ping-pong example:

```
use Porter\Server;
use Porter\Payload;
use Porter\Events\AbstractEvent;
use Porter\Connection;

class Ping extends AbstractEvent
{
    public static string $type = 'ping';

    public function handle(Connection $connection, Payload $payload, Server $server): void
    {
        $this->reply('pong');
    }
}

// and next you need add (register) this class to events:
server()->addEvent(Ping::class);
```

> **NOTICE:** The event class must have a `handle()` method.
>
> This method handles the event. You can also create other methods.

#### `AbstractEvent`

[](#abstractevent)

#### Properties

[](#properties-2)

Each child class get following properties:

- `Connection $connection` - from whom the event came;
- `Payload $payload` - contain data from client;
- `Server $server` - server instance;
- `Collection $data` - short cut for payload data (as &amp;link).;

#### Magic properties &amp; methods.

[](#magic-properties--methods)

If client pass in data `channel_i_d` with channel id or `target_id` with id of connection, we got a magic properties and methods.

```
// this is a object of Channel, getted by `channel_id` from client.
$this->channel;
$this->channel();

$this->channel()->broadcast('new message', [
    'text' => $this->payload->get('text'),
    'from' => $this->connection->nickname,
]);
```

```
// this is a object of Channel, getted by `target_id` from client.
$this->target;
$this->target();

$this->to($this->target, 'new message', [
    'text' => $this->payload->get('text'),
    'from' => $this->connection->nickname,
]);
```

#### Methods

[](#methods)

##### `to(TcpConnection|Connection|array $connection, string $event, array $data = []): self`

[](#totcpconnectionconnectionarray-connection-string-event-array-data---self-1)

Send event to connection.

```
$this->to($connection, 'ping');
```

##### `reply(string $event, array $data = []): ?bool`

[](#replystring-event-array-data---bool)

Reply event to incoming connection.

```
$this->reply('ping');

// analog for:
$this->to($this->connection, 'ping');
```

To reply with the current `type`, pass only the `$data` parameter.

**On front-end:**

```
client.send('hello to', {username: 'John Doe'}, payload => {
    console.log(payload.data.message); // Hello, John Doe!
});
```

**On back-end:**

```
$username = $this->payload->data['username'];
$this->reply(data: ['message' => "Hello, {$username}!"]);
```

##### `raw(string $string): bool|null`

[](#rawstring-string-boolnull)

Send raw data to connection. Not a event object.

```
$this->raw('ping');

// now client will receive just a 'ping', not event object.
```

##### `broadcast(string $event, array $data = [], TcpConnection|Connection|array $excepts = []): void`

[](#broadcaststring-event-array-data---tcpconnectionconnectionarray-excepts---void)

Send event to all connections.

Yes, to **all connections** on server.

```
$this->broadcast('chat message', [
    'nickname' => 'John Doe',
    'message' => 'Hello World!',
]);
```

Send event to all except for the connection from which the event came.

```
$this->broadcast('user join', [
    'text' => 'New user joined to chat.',
], [$this->connection]);
```

#### `validate(): bool`

[](#validate-bool)

Validate payload data.

Pass custom rules. Default use $rules class attribute.

Returns `false` if has errors.

```
if (!$this->validate()) {
    return $this->reply(/** ... */);
}
```

#### `hasErrors(): bool`

[](#haserrors-bool)

Returns `true` if has errors on validate payload data.

```
if ($this->hasErrors()) {
    return $this->reply('bad request', ['errors' => $this->errors]);
}
```

```
// $this->errors contains:

^ array:1 [
  "username" => array:1 [
    "length" => "username failed validation: length"
  ]
]

```

#### `payload(string $key, mixed $default = null): mixed`

[](#payloadstring-key-mixed-default--null-mixed-1)

Yet another short cut for payload data.

```
public function handle(Connection $connection, Payload $payload, Server $server)
{
    $this->get('nickname');

    // as property
    $this->data['nickname'];
    $this->data->get('nickname');

    // form payload instance
    $payload->data['nickname'];
    $payload->data->get('nickname');

    $this->payload->data['nickname'];
    $this->payload->data->get('nickname');
}
```

#### `Anonymous function`

[](#anonymous-function)

In anonymous function instead of `$this`, use `$event`.

```
use Porter\Events\Event;

server()->on('new message', function (Event $event) {
    // $event has all the same property && methods as in the example above

    $event->to($event->target, 'new message', [
        'text' => $this->payload->get('text'),
        'from' => $this->connection->nickname,
    ]);

    $event->channel()->broadcast('new message', [
        'text' => $this->payload->get('text'),
        'from' => $this->connection->nickname,
    ]);
});
```

🔹 `TcpConnection|Connection $connection`
----------------------------------------

[](#-tcpconnectionconnection-connection)

It is a global object, changing in one place, it will contain the changed data in another place.

This object has already predefined properties:

See all `$connection` methods [here](https://doc.hotexamples.com/class/workerman.connection/TcpConnection).

You can set different properties, functions to this object.

```
$connection->firstName = 'John';
$connection->lastName = 'Doe';
$connection->getFullName = fn () => $connection->firstName . ' ' . $connection->lastName;

call_user_func($connection->getFullname); // John Doe
```

### Custom property `channels`

[](#custom-property-channels)

```
$connection->channels; // object of Porter\Connection\Channels
```

#### List of methods `Porter\Connection\Channels`

[](#list-of-methods-porterconnectionchannels)

```
/**
 * Get connection channels.
 *
 * @return Channel[]
 */
public function all(): array
```

```
/**
 * Get channels count.
 *
 * @return int
 */
public function count(): int
```

```
/**
 * Method for when connection join to channel should detach channel id from connection.
 *
 * @param string $channelId
 * @return void
 */
public function delete(string $channelId): void
```

```
/**
 * Leave all channels for this connection.
 *
 * @return void
 */
public function leaveAll(): void
```

> **NOTICE:** On disconnect client connection will leave of all the channels where he was.

```
/**
 * When connection join to channel should attach channel id to connection.
 *
 * You don't need to use this method, it will automatically fire inside the class.
 *
 * @param string $channelId
 * @return void
 */
public function add(string $channelId): void
```

🔹 `Client (PHP)`
----------------

[](#-client-php)

Simple implementation of client.

See basic example of client [here](/examples/client-php/client.php).

##### `__construct(string $host, array $context = [])`

[](#__constructstring-host-array-context--)

Create client.

```
$client = new Client('ws://localhost:3737');
$client = new Client('wss://example.com:3737');
```

##### `setWorker(Worker $worker): void`

[](#setworkerworker-worker-void-2)

Set worker.

> **NOTICE:** Worker instance auto init in constructor. Use this method if you need to define worker with specific settings.

##### `getWorker(): Worker`

[](#getworker-worker-1)

Get worker.

##### `send(string $type, array $data = []): ?bool`

[](#sendstring-type-array-data---bool)

Send event to server.

```
$client->on('ping', function (AsyncTcpConnection $connection, Payload $payload, Client $client) {
    $client->send('pong', ['time' => time()]);
});
```

##### `raw(string $payload): ?bool`

[](#rawstring-payload-bool)

Send raw payload to server.

```
$client->raw('simple message');
```

##### `onConnected(callable $handler): void`

[](#onconnectedcallable-handler-void-1)

Emitted when a socket connection is successfully established.

```
$client->onConnected(function (AsynTcpConnection $connection) {
    //
});
```

##### `onDisconnected(callable $handler): void`

[](#ondisconnectedcallable-handler-void-1)

Emitted when the server sends a FIN packet.

```
$client->onDisconnected(function (AsynTcpConnection $connection) {
    //
});
```

##### `onError(callable $handler): void`

[](#onerrorcallable-handler-void-1)

Emitted when an error occurs with connection.

```
$client->onError(function (AsyncTcpConnection $connection, $code, $message) {
    //
});
```

##### `onRaw(callable $handler): void`

[](#onrawcallable-handler-void-1)

Handle non event messages (raw data).

```
$client->onRaw(function (string $payload, AsyncTcpConnection $connection) {
    if ($payload == 'ping') {
        $connection->send('pong');
    }
});
```

##### `on(string $type, callable $handler): void`

[](#onstring-type-callable-handler-void-1)

Event handler as callable.

```
$client->on('pong', function (AsyncTcpConnection $connection, Payload $payload, Client $client) {
    //
});
```

##### `listen(): void`

[](#listen-void)

Connect to server and listen.

```
$client->listen();
```

🔹 `Storage`
-----------

[](#-storage)

Storage is a part of server, all data stored in flat files.

To get started you need set a path where files will be stored.

```
server()->storage()->load(__DIR__ . '/server-storage.data'); // you can use any filename
```

You can get access to storage like property or method:

```
server()->storage();
```

> **NOTICE:** Set path only after if you booting server by (`server()->boot($worker)` method, `Storage::class` can use anywhere and before booting server.

> **WARNING:** If you not provide path or an incorrect path, data will be stored in RAM. After server restart you lose your data.

#### `Storage::class`

[](#storageclass)

```
// as standalone use without server
$store1 = new Porter\Storage(__DIR__ . '/path/to/file1');
$store2 = new Porter\Storage(__DIR__ . '/path/to/file2');
$store3 = new Porter\Storage(__DIR__ . '/path/to/file3');
```

#### `load(?string $path = null): self`

[](#loadstring-path--null-self)

```
server()->storage()->load(__DIR__ . '/path/to/file'); // you can use any filename
```

#### `put(string $key, mixed $value): void`

[](#putstring-key-mixed-value-void)

```
server()->storage()->put('foo', 'bar');
```

#### `get(string $key, mixed $default = null): mixed`

[](#getstring-key-mixed-default--null-mixed)

```
server()->storage()->get('foo', 'default value'); // foo
server()->storage()->get('baz', 'default value'); // default value
```

#### `remove(string ...$keys): self`

[](#removestring-keys-self)

```
server()->storage()->remove('foo'); // true
```

#### `has(string $key): bool`

[](#hasstring-key-bool)

```
server()->storage()->has('foo'); // true
server()->storage()->has('baz'); // false
```

#### `filename(): string`

[](#filename-string)

Returns path to file.

```
server()->storage()->getPath();
```

🔹 Helpers (functions)
---------------------

[](#-helpers-functions)

#### `server(): Server`

[](#server-server)

```
server()->on(...);

// will be like:
use Porter\Server;
Server::getInstance()->on(...);
```

#### `worker(): Worker`

[](#worker-worker)

```
worker()->connections;

// will be like:
use Porter\Server;
Server::getInstance()->getWorker()->connections;
```

#### `channel(string $id, string|array $key = null, mixed $default = null): mixed`

[](#channelstring-id-stringarray-key--null-mixed-default--null-mixed)

```
$channel = channel('secret channel'); // get channel instance
$channel = server()->channel('secret channel');
$channel = server()->channels()->get('secret channel');
```

💡 See all helpers [here](/helpers.php).

🔹 Mappable methods (Macros)
---------------------------

[](#-mappable-methods-macros)

You can extend the class and map your own methods on the fly..

Basic method:

```
server()->map('sum', fn(...$args) => array_sum($args));
echo server()->sum(1000, 300, 30, 5, 2); // 1337
echo server()->sum(1000, 300, 30, 5, 3); // 1338
```

As singletone method:

```
server()->mapOnce('timestamp', fn() => time());
echo server()->timestamp(); // e.g. 1234567890
sleep(1);
echo server()->timestamp(); // e.g. 1234567890
```

🔹 Front-end
===========

[](#-front-end)

There is also a [small class](#javascript) for working with websockets on the client side.

```
if (location.hostname == '127.0.0.1' || location.hostname == 'localhost') {
    const ws = new WebSocket(`ws://${location.hostname}:3737`); // on local dev
} else {
    const ws = new WebSocket(`wss://${location.hostname}:3737`); // on vps with ssl certificate
}

// options (optional, below default values)
let options = {
    pingInterval: 30000, // 30 sec.
    maxBodySize: 1e+6, // 1 mb.
}

const client = new Porter(ws, options);

// on client connected to server
client.connected = () => {
    // code...
}

// on client disconected from server
client.disconnected = () => {
    // code...
}

// on error
client.error = () => {
    // code...
}

// on raw `pong` event (if you needed it)
client.pong = () => {
    // code...
}

// close connection
client.close();

// event handler
client.on('ping', payload => {
    // available properties
    payload.type;
    payload.data;

    console.log(payload.data.foo) // bar
});

// send event to server
client.send('pong', {
    foo: 'bar',
});

// chain methods
client.send('ping').on('pong', payload => console.log(payload.type));

// send event and handle answer in one method
client.send('get online users', {/* ... */}, payload => {
    console.log(payload.type); // contains same event type 'get online users'
    console.log(payload.data.online); // and server answer e.g. '1337 users'
});

// pass channel_id and target_id for magic properties on back-end server
client.send('magical properties example', {
    channel_id: 'secret channel',
    target_id: 1337,

    // on backend php websocket server we can use $this->channel and $this->target magical properties.
});

// send raw websocket data
client.raw.send('hello world');

// send raw websocket data as json
client.raw.send(JSON.stringify({
    foo: 'bar',
}));

// handle raw websocket data from server
client.raw.on('hello from server', data => {
    console.log(data); // hello from server
});

// dont forget start listen websocket server!
client.listen();
```

Used by
=======

[](#used-by)

- [naplenke.online](https://naplenke.online?ref=porter) — The largest online cinema in Russia. Watching movies together.

Credits
=======

[](#credits)

- [Chipslays](https://github.com/chipslays)
- [Workerman](https://github.com/walkor/workerman) by [walkor](https://github.com/walkor)
- [All contributors](https://github.com/chipslays/porter/graphs/contributors)

License
=======

[](#license)

MIT

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance30

Infrequent updates — may be unmaintained

Popularity12

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity75

Established project with proven stability

 Bus Factor1

Top contributor holds 99.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Recently: every ~84 days

Total

111

Last Release

895d ago

Major Versions

1.2.9 → 2.x-dev2024-01-19

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/19103498?v=4)[chipslays](/maintainers/chipslays)[@chipslays](https://github.com/chipslays)

---

Top Contributors

[![chipslays](https://avatars.githubusercontent.com/u/19103498?v=4)](https://github.com/chipslays "chipslays (386 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

phpphp-websocketsocketwebsocket-clientwebsocket-librarywebsocket-serversocketswebsocketrealtimechat

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/chipslays-porter/health.svg)

```
[![Health](https://phpackages.com/badges/chipslays-porter/health.svg)](https://phpackages.com/packages/chipslays-porter)
```

###  Alternatives

[cboden/ratchet

PHP WebSocket library

6.4k22.3M254](/packages/cboden-ratchet)[centrifugal/phpcent

PHP library to communicate with Centrifugo HTTP API

1852.5M4](/packages/centrifugal-phpcent)[basement-chat/basement-chat

Add a real-time chat widget to your Laravel application.

4984.0k](/packages/basement-chat-basement-chat)[sl4mmer/phpcent

PHP library to communicate with Centrifugo HTTP API

185296.5k1](/packages/sl4mmer-phpcent)[morozovsk/websocket

simple php websocket server with examples and demo: simple chat (single daemon) - http://sharoid.ru/chat.html , pro chat (master + worker) - http://sharoid.ru/chat2.html , simple game - http://sharoid.ru/game.html

36617.8k2](/packages/morozovsk-websocket)[morozovsk/yii2-websocket

simple php websocket server with examples and demo: simple chat (single daemon) - http://sharoid.ru/chat.html , pro chat (master + worker) - http://sharoid.ru/chat2.html , simple game - http://sharoid.ru/game.html

9515.3k](/packages/morozovsk-yii2-websocket)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
