Untyped RPC
“Untyped” means that you don't need any PHP classes representing TL schema: you must use mixed[]
hashmaps to send and receive queries.
Slightly easier to start, generally it is not a recommended practice: typed RPC works much more performant, especially for huge amounts of traffic and vectors of numbers.
Let's start with an example
Suppose we have a ‘messages' engine that can execute the following query:
---types---
messages.inviteResult user_id:int already_in_chat:bool = messages.InviteResult;
---functions---
messages.inviteUsersToChat chat_id:long user_ids:%(Vector int) silent:bool = Vector %messages.InviteResult;
Here is how we send a request:
// this will be mixed[]
$query = [
'_' => 'messages.inviteUsersToChat', // function name
'chat_id' => 123456, // long in TL => int in PHP
'user_ids' => [190, 336098765], // Vector int in TL => int[] in PHP
'silent' => false, // bool in TL => bool in PHP
];
$connection = new_rpc_connection($host, $port);
$query_id = rpc_tl_query_one($connection, $query); // int
Here is how we get and parse the response:
$response = rpc_tl_query_result_one($query_id); // mixed[]
// check for errors
if (isset($response['__error'])) {
// __error, __error_code
}
// ['result'] is valid unless error
$result = $response['result']; // mixed
// it represents TL structure, here — Vector %messages.InviteResult, an array of arrays (hashmaps)
foreach ($result as $row) { // we know, that $row is messages.InviteResult
$row['user_id']; // mixed (int at runtime)
$row['already_in_chat']; // mixed (bool at runtime)
}
Non-associative queries
Above, we assigned $query to
$query = ['_' => '...', 'chat_id' => 123456, /* ... */ ];
If to use a vector instead of a map, its behavior will be identical:
$query = [
'messages.inviteUsersToChat', // dynamic parsing will enumerate the rest in TL schema order:
123456, // chat_id
[190, 336098765], // user_ids
false, // silent
];
This seems shorter but is harder to read when you return to this code after weeks.
Types with multiple constructors (polymorphic types)
Let's consider the following TL fragment:
---types---
memcache.not_found = memcache.Value;
memcache.str_value value:string = memcache.Value;
memcache.numeric_value value:long = memcache.Value;
---functions---
memcache.get key:string = memcache.Value;
Here memcache.Value is a polymorphic type, so we need to handle this on response parsing:
$result = $response['result']; // mixed; it's memcache.Value
// $result is one of the following here:
// ['_' => 'memcache.not_found']
// ['_' => 'memcache.str_value', 'value' => (string)]
// ['_' => 'memcache.numeric_value', 'value' => (int)]
$constructor_name = $result['_'];
switch($constructor_name) { /* ... */ }
// or, maybe this is enough in our particular case
$value = $result['value']; // null / string / int
Field masks
Now let's investigate, how to deal with bitmasks of sent/requested fields.
---types---
fileStorage.localCopy {fields_mask:#}
hash_id:int
cached_at:fields_mask.0?int
available:fields_mask.1?Bool
last_sync_info:fields_mask.2?(Maybe %fileStorage.SyncInfo)
= fileStorage.LocalCopy fields_mask;
---functions---
fileStorage.getAllLocalCopies
fields_mask:#
file_name:string
= Vector %(fileStorage.LocalCopy fields_mask);
As we see, fields_mask is a request parameter determining what fields we want to get from an engine.
$query = [
'_' => 'fileStorage.getAllLocalCopies',
'fields_mask' => (1<<0) | (1<<2), // request 'cached_at' and 'last_sync_info'
'file_name' => '...'
];
Each row in response will we a hashmap with fields we requested:
foreach ($result as $local_copy) {
$local_copy['hash_id']; // always exists
$local_copy['cached_at']; // exists, as 0 bit was requested
/*$local_copy['available']*/ // not set, as 1 bit was not set
$local_copy['last_sync_info']; // it's Maybe T — requested, but may not exist due to TL schema
}
How do TL types map onto ‘mixed' internals
There are several built-in TL types, which you use to describe your TL types and functions.
TL type | PHP runtime Type |
---|---|
int, # | int (when storing more than 2^31, cut from C++ int64_t to C++ int) |
long | int |
float | float (when storing, converting to C++ float from C++ double) |
double | float |
string | string |
bool | bool |
True | bool |
Vector<T> | mixed[] (array-vector) |
Maybe<T> | null or T representation |
Tuple<T>(n) | mixed[] (array-vector) |
Dictionary<T> | mixed[] (array-associative) |
{t:Type} | Just mixed[] as if no dependence |
fields_mask | mixed (null or value at runtime) |
!X | mixed[] (nested request) |
=X | mixed[] (nested response) |
API reference for untyped TL
To use TL/RPC in plain PHP, vkext should be installed.
Executes a query, like in the examples above. Returns query id.
Executes multiple queries at once. Returns vector of query ids.
Wait for a query response, like in the examples above. Returns an array with either ‘result' or ‘__error'.
Waits for multiple responses at once. Returns an associative array keyed with query_id and valued like above.
Like, rpc_tl_query_result(), but not resumable.
Returns a number of queries that have been sent, but not received yet.
Manual RPC request storing and fetching
There are a number of low-level functions (see functions.txt for full list):
- store_int(), store_string() and others for TL built-in types
- fetch_int(), fetch_string() and company
- fetch_lookup_int() — gets an integer at buffer pointer, but doesn't offset fetching position
They allow you to manually prepare RPC output buffer and parse back received bytes — without TL schema.
Internal KPHP network layer and RPC implementation is the basics for async programming, because rpc_tl_query_result() and rpc_tl_query_result_one() are resumable.
When the async model is combined with strict typing, a really great performance can be achieved. Click “Next”.