Skip to content

Commit ffbc978

Browse files
php examples
1 parent 619d2b5 commit ffbc978

File tree

12 files changed

+758
-1
lines changed

12 files changed

+758
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
/helpers
1010
__pycache__/
1111
*.py[cod]
12-
/examples/php
1312
/examples/java
1413
/examples/node
1514
/examples/go

examples/php/helloworld/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Neutral TS Hello World PHP example
2+
==================================
3+
4+
Command line Hello World package example
5+
6+
Requirements:
7+
- IPC server: [Neutral TS IPC Server](https://github.com/FranBarInstance/neutral-ipc/releases)
8+
- PHP IPC client: [Neutral TS IPC Clients](https://github.com/FranBarInstance/neutral-ipc/clients)
9+
- PHP 7.4 or higher
10+
- JSON extension enabled
11+
12+
Run:
13+
14+
```
15+
php helloworld.php
16+
```
17+
18+
Links
19+
-----
20+
21+
Neutral TS template engine.
22+
23+
- [Template docs](https://github.com/FranBarInstance/neutralts-docs/docs/neutralts/doc/)
24+
- [Repository](https://github.com/FranBarInstance/neutralts)
25+
- [Crate](https://crates.io/crates/neutralts)
26+
- [Examples](https://github.com/FranBarInstance/neutralts-docs/tree/master/examples)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Neutral TS Hello World PHP example
4+
* https://github.com/FranBarInstance/neutralts-docs/
5+
*/
6+
7+
# Include NeutralIpcTemplate: https://github.com/FranBarInstance/neutral-ipc/clients
8+
include '../ipc-client/NeutralIpcTemplate.php';
9+
10+
# The schema contains among other things the data and variables for the template
11+
$schema = [
12+
"data" => [
13+
"hello" => "Hello World"
14+
]
15+
];
16+
17+
// Determine the template path
18+
$template = __DIR__ . "/template.ntpl";
19+
20+
# Pass the schema as json to NeutralIpcTemplate
21+
$schema_json = json_encode($schema);
22+
23+
# Create an instance of NeutralTemplate
24+
$ipc_template = new NeutralIpcTemplate($template, $schema_json);
25+
26+
# Render the template
27+
$contents = $ipc_template->render();
28+
29+
# Print the rendered content, in other cases contents will be sent to output according to framework.
30+
echo $contents . PHP_EOL;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
From PHP example: {:;hello:}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
// Configuration module for Neutral IPC client.
4+
// Reads configuration from /etc/neutral-ipc-cfg.json or uses default values.
5+
// neutral-ipc-cfg.json is the configuration file used by the IPC server.
6+
// https://github.com/FranBarInstance/neutral-ipc
7+
8+
class NeutralIpcConfig {
9+
const HOST = '127.0.0.1';
10+
const PORT = '4273';
11+
const TIMEOUT = 5;
12+
const BUFFER_SIZE = 8192;
13+
14+
private static $configLoaded = false;
15+
private static $configValues = [];
16+
17+
public static function getHost() {
18+
self::loadConfig();
19+
return self::$configValues['host'] ?? self::HOST;
20+
}
21+
22+
public static function getPort() {
23+
self::loadConfig();
24+
return self::$configValues['port'] ?? self::PORT;
25+
}
26+
27+
public static function getTimeout() {
28+
self::loadConfig();
29+
return self::$configValues['timeout'] ?? self::TIMEOUT;
30+
}
31+
32+
public static function getBufferSize() {
33+
self::loadConfig();
34+
return self::$configValues['buffer_size'] ?? self::BUFFER_SIZE;
35+
}
36+
37+
private static function loadConfig() {
38+
if (self::$configLoaded) {
39+
return;
40+
}
41+
42+
$configFile = '/etc/neutral-ipc-cfg.json';
43+
44+
if (file_exists($configFile) && is_readable($configFile)) {
45+
try {
46+
$configContent = file_get_contents($configFile);
47+
$configData = json_decode($configContent, true);
48+
49+
if (json_last_error() === JSON_ERROR_NONE && is_array($configData)) {
50+
self::$configValues = $configData;
51+
}
52+
} catch (Exception $e) {
53+
// Silently fall back to default values if config file is invalid
54+
error_log("NeutralIpcConfig: Error loading config file: " . $e->getMessage());
55+
}
56+
}
57+
58+
self::$configLoaded = true;
59+
}
60+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php
2+
/**
3+
* PHP IPC client for Neutral TS.
4+
* https://github.com/FranBarInstance/neutral-ipc
5+
*/
6+
7+
include 'NeutralIpcConfig.php';
8+
9+
class NeutralIpcRecord
10+
{
11+
# ============================================
12+
# Neutral IPC record version 0 (draft version)
13+
# ============================================
14+
#
15+
# HEADER:
16+
#
17+
# \x00 # reserved
18+
# \x00 # control (action/status) (10 = parse template)
19+
# \x00 # content-format 1 (10 = JSON, 20 = file path, 30 = plaintext, 40 = binary)
20+
# \x00\x00\x00\x00 # content-length 1 big endian byte order
21+
# \x00 # content-format 2 (10 = JSON, 20 = file path, 30 = plaintext, 40 = binary)
22+
# \x00\x00\x00\x00 # content-length 2 big endian byte order (can be zero)
23+
#
24+
# All text utf8
25+
26+
const RESERVED = 0;
27+
const HEADER_LEN = 12;
28+
const CTRL_PARSE_TEMPLATE = 10;
29+
const CTRL_STATUS_OK = 0;
30+
const CTRL_STATUS_KO = 1;
31+
const CONTENT_JSON = 10;
32+
const CONTENT_PATH = 20;
33+
const CONTENT_TEXT = 30;
34+
const CONTENT_BIN = 40;
35+
36+
public static function decodeHeader($header)
37+
{
38+
$header_array = unpack("creserved/ccontrol/cformat-1/Nlength-1/cformat-2/Nlength-2", $header);
39+
40+
return [
41+
'reserved' => $header_array['reserved'],
42+
'control' => $header_array['control'],
43+
'format-1' => $header_array['format-1'],
44+
'length-1' => $header_array['length-1'],
45+
'format-2' => $header_array['format-2'],
46+
'length-2' => $header_array['length-2'],
47+
];
48+
}
49+
50+
public static function encodeHeader($control, $format1, $length1, $format2, $length2)
51+
{
52+
return pack("CCCNCN",
53+
self::RESERVED,
54+
$control,
55+
$format1,
56+
$length1,
57+
$format2,
58+
$length2
59+
);
60+
}
61+
62+
public static function encodeRecord($control, $format1, $content1, $format2, $content2)
63+
{
64+
$length1 = strlen($content1);
65+
$length2 = strlen($content2);
66+
$header = self::encodeHeader($control, $format1, $length1, $format2, $length2);
67+
$record = $header . $content1 . $content2;
68+
69+
return $record;
70+
}
71+
72+
public static function decodeRecord($header, $content1, $content2)
73+
{
74+
return [
75+
'reserved' => ord($header[0]),
76+
'control' => ord($header[1]),
77+
'format-1' => ord($header[2]),
78+
'content-1' => $content1,
79+
'format-2' => ord($header[7]),
80+
'content-2' => $content2,
81+
];
82+
}
83+
}
84+
85+
class NeutralIpcClient
86+
{
87+
protected $control;
88+
protected $format1;
89+
protected $content1;
90+
protected $format2;
91+
protected $content2;
92+
protected $result;
93+
protected $stream;
94+
95+
public function __construct(string $control, string $format1, string $content1, string $format2, string $content2)
96+
{
97+
$this->control = $control;
98+
$this->format1 = $format1;
99+
$this->content1 = $content1;
100+
$this->format2 = $format2;
101+
$this->content2 = $content2;
102+
$this->result = [];
103+
}
104+
105+
public function start()
106+
{
107+
$context = stream_context_create([
108+
'socket' => [
109+
'timeout' => NeutralIpcConfig::getTimeout(),
110+
],
111+
]);
112+
113+
$this->stream = stream_socket_client(
114+
"tcp://" . NeutralIpcConfig::getHost() . ":" . NeutralIpcConfig::getPort(),
115+
$errno,
116+
$errstr,
117+
NeutralIpcConfig::getTimeout(),
118+
STREAM_CLIENT_CONNECT,
119+
$context
120+
);
121+
122+
if ($this->stream === false) {
123+
throw new Exception("Connection failed: $errstr ($errno)");
124+
}
125+
126+
$request = NeutralIpcRecord::encodeRecord(
127+
$this->control, $this->format1, $this->content1, $this->format2, $this->content2
128+
);
129+
fwrite($this->stream, $request);
130+
131+
$response_header = fread($this->stream, NeutralIpcRecord::HEADER_LEN);
132+
if (strlen($response_header) !== NeutralIpcRecord::HEADER_LEN) {
133+
fclose($this->stream);
134+
throw new Exception("Incomplete header received");
135+
}
136+
137+
$response = NeutralIpcRecord::decodeHeader($response_header);
138+
139+
$readContent = function($stream, $length) {
140+
$content = '';
141+
$bufferSize = NeutralIpcConfig::getBufferSize();
142+
while ($length > 0) {
143+
$chunkSize = min($bufferSize, $length);
144+
$chunk = fread($stream, $chunkSize);
145+
if ($chunk === false) {
146+
throw new Exception("Error reading from stream");
147+
}
148+
$content .= $chunk;
149+
$length -= strlen($chunk);
150+
}
151+
return $content;
152+
};
153+
154+
$content1 = $readContent($this->stream, $response['length-1']);
155+
if (strlen($content1) !== $response['length-1']) {
156+
throw new Exception("Incomplete content-1 received");
157+
}
158+
159+
$content2 = $readContent($this->stream, $response['length-2']);
160+
if (strlen($content2) !== $response['length-2']) {
161+
throw new Exception("Incomplete content-2 received");
162+
}
163+
164+
$this->result = NeutralIpcRecord::decodeRecord($response_header, $content1, $content2);
165+
166+
return $this->result;
167+
}
168+
}
169+
170+
class NeutralIpcTemplate
171+
{
172+
protected $template;
173+
protected $tpltype; // template type CONTENT_PATH (file) or CONTENT_TEXT (raw source)
174+
protected $schema; // array schema or string json schema
175+
protected $result = [];
176+
177+
public function __construct(string $template, mixed $schema, int $tpltype = NeutralIpcRecord::CONTENT_PATH)
178+
{
179+
$this->template = $template;
180+
$this->tpltype = $tpltype;
181+
182+
if (is_string($schema)) {
183+
$this->schema = $schema;
184+
} else {
185+
$this->schema = json_encode($schema);
186+
}
187+
}
188+
189+
public function render()
190+
{
191+
$record = new NeutralIpcClient(
192+
NeutralIpcRecord::CTRL_PARSE_TEMPLATE,
193+
NeutralIpcRecord::CONTENT_JSON,
194+
$this->schema,
195+
$this->tpltype,
196+
$this->template
197+
);
198+
199+
$result = $record->start();
200+
201+
$this->result = [
202+
'status' => $result['control'],
203+
'result' => json_decode($result['content-1'], true),
204+
'content' => $result['content-2'],
205+
];
206+
207+
return $this->result['content'];
208+
}
209+
210+
public function set_path(string $path)
211+
{
212+
$this->tpltype = NeutralIpcRecord::CONTENT_PATH;
213+
$this->template = $path;
214+
}
215+
216+
public function set_source(string $source)
217+
{
218+
$this->tpltype = NeutralIpcRecord::CONTENT_TEXT;
219+
$this->template = $source;
220+
}
221+
222+
public function merge_schema(mixed $schema)
223+
{
224+
$new_schema = [];
225+
226+
if (is_string($schema)) {
227+
$new_schema = array_replace_recursive(json_decode($this->schema, true), json_decode($schema, true));
228+
} else {
229+
$new_schema = array_replace_recursive(json_decode($this->schema, true), $schema);
230+
}
231+
232+
$this->schema = json_encode($new_schema);
233+
}
234+
235+
public function has_error()
236+
{
237+
if ($this->result['status'] != 0 || $this->result['result']['has_error']) {
238+
return true;
239+
} else {
240+
return false;
241+
}
242+
}
243+
244+
public function get_status_code()
245+
{
246+
return $this->result['result']['status_code'] ?? null;
247+
}
248+
249+
public function get_status_text()
250+
{
251+
return $this->result['result']['status_text'] ?? null;
252+
}
253+
254+
public function get_status_param()
255+
{
256+
return $this->result['result']['status_param'] ?? null;
257+
}
258+
259+
public function get_result()
260+
{
261+
return $this->result['result'] ?? null;
262+
}
263+
}

0 commit comments

Comments
 (0)