Skip to content

Commit db22d1c

Browse files
committed
Refactor for better naming and error checking
1 parent 5ded013 commit db22d1c

File tree

9 files changed

+108
-62
lines changed

9 files changed

+108
-62
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ composer.phar
77
.idea
88
.vscode
99
.phpunit.cache
10+
11+
demo.txt
12+
demo-file.tx

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ It uses a combination of:
1818
- Symfony/Process lib
1919
- and PHP's native Shmop extension
2020

21+
**Warning: it does not works on MSYS/MINGW terminals!**. It will work fine on both Windows (cmd and powershell) and Linux.
22+
2123
See [demos/demo.php](demos/demo.php) for examples.
2224

2325
## Installation
@@ -33,7 +35,7 @@ composer require terremoth/php-async
3335

3436
require_once 'vendor/autoload.php';
3537

36-
use Terremoth\Async\File;
38+
use Terremoth\Async\PhpFile;
3739
use Terremoth\Async\Process;
3840

3941
$process = new Process();
@@ -49,7 +51,7 @@ $process->send(function () {
4951
});
5052

5153
$args = ['--verbose', '-n', '123'];
52-
$asyncFile = new File('existing-php-file.php', $args); // make sure to pass the correct file with its path
54+
$asyncFile = new PhpFile('existing-php-file.php', $args); // make sure to pass the correct file with its path
5355
$asyncFile->run();
5456

5557
```

composer.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"php": "^8.2",
77
"symfony/process": "^7.2",
88
"laravel/serializable-closure": "^2.0",
9-
"ext-shmop": "*"
9+
"ext-shmop": "*",
10+
"ext-fileinfo": "*",
11+
"ext-zlib": "*"
1012
},
1113
"keywords": [
1214
"async",

demos/demo.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
require_once 'vendor/autoload.php';
44

55
use Terremoth\Async\Process;
6-
use Terremoth\Async\File;
6+
use Terremoth\Async\PhpFile;
77

88
$process = new Process();
99
echo date('c') . ' :: Sending process. You should not wait any longer to see next message: ' . PHP_EOL;
1010
try {
1111
$process->send(function () {
1212

1313
$var1 = 4;
14-
$var2 = 2 + 2;
14+
$var2 = ((2 + 2) / $var1) + ((2 * 2) - 1);
1515

1616
sleep(5);
1717

@@ -28,7 +28,7 @@
2828
echo date('c') . ' :: Now let\'s process a file that takes a long time...' . PHP_EOL;
2929

3030
try {
31-
$file = new File(__DIR__ . DIRECTORY_SEPARATOR . 'time-wasting-file.php');
31+
$file = new PhpFile(__DIR__ . DIRECTORY_SEPARATOR . 'time-wasting-file.php');
3232
$file->run();
3333
echo date('c') . ' :: Ended...' . PHP_EOL;
3434
} catch (Exception $e) {

src/Terremoth/Async/File.php

-34
This file was deleted.

src/Terremoth/Async/PhpFile.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Terremoth\Async;
4+
5+
use Exception;
6+
use InvalidArgumentException;
7+
use Symfony\Component\Process\Process as SymfonyProcess;
8+
9+
readonly class PhpFile
10+
{
11+
/**
12+
* @param string $file
13+
* @throws Exception
14+
* @param array<array-key, null>|list{int} $args
15+
*/
16+
public function __construct(private string $file, private array $args = [])
17+
{
18+
if (!is_readable($this->file)) {
19+
throw new InvalidArgumentException('Error: file ' . $this->file
20+
. ' does not exists or is not readable!');
21+
}
22+
23+
$finfo = finfo_open(FILEINFO_MIME_TYPE);
24+
$mimeType = finfo_file($finfo, $this->file);
25+
finfo_close($finfo);
26+
27+
if (!in_array($mimeType, ['text/x-php', 'application/x-php', 'application/php', 'application/x-httpd-php'])) {
28+
throw new Exception('Error: file ' . $this->file . ' is not a PHP file!');
29+
}
30+
}
31+
32+
public function run(): void
33+
{
34+
if (PHP_OS_FAMILY === 'Windows') {
35+
$template = ['start', "", '/B', PHP_BINARY, $this->file, ...$this->args];
36+
$process = new SymfonyProcess($template);
37+
$process->start();
38+
return;
39+
}
40+
41+
$args = implode(' ', $this->args);
42+
exec(PHP_BINARY . ' ' . $this->file . ' ' . $args . ' > /dev/null 2>&1 &');
43+
}
44+
}

src/Terremoth/Async/Process.php

+13-20
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55
use Closure;
66
use Exception;
77
use Laravel\SerializableClosure\SerializableClosure;
8-
use Symfony\Component\Process\Process as SymfonyProcess;
98

109
class Process
1110
{
1211
private const MAX_INT = 2147483647;
1312

14-
public function __construct(private int $key = 0)
13+
public function __construct(private int $shmopKey = 0)
1514
{
16-
if (!$this->key) {
17-
$this->key = mt_rand(0, self::MAX_INT); // communication key
15+
if (!$this->shmopKey) {
16+
$this->shmopKey = mt_rand(0, self::MAX_INT); // communication key
1817
}
1918
}
2019

@@ -23,30 +22,24 @@ public function __construct(private int $key = 0)
2322
*/
2423
public function send(Closure $asyncFunction): void
2524
{
26-
$separator = DIRECTORY_SEPARATOR;
25+
$dirSlash = DIRECTORY_SEPARATOR;
2726
$serialized = serialize(new SerializableClosure($asyncFunction));
28-
$serializedLength = strlen($serialized);
29-
$shmopInstance = shmop_open($this->key, 'c', 0660, $serializedLength);
27+
$compressedLength = mb_strlen($serialized);
28+
$shmopInstance = shmop_open($this->shmopKey, 'c', 0660, $compressedLength);
3029

3130
if (!$shmopInstance) {
32-
throw new Exception('Could not create shmop instance with key: ' . $this->key);
31+
throw new Exception('Could not create shmop instance with key: ' . $this->shmopKey);
3332
}
3433

3534
$bytesWritten = shmop_write($shmopInstance, $serialized, 0);
3635

37-
if ($bytesWritten != $serializedLength) {
38-
throw new Exception('Could not write the entire data to shared memory with length: ' .
39-
$serializedLength . '. Bytes written: ' . $bytesWritten);
36+
if ($bytesWritten < $compressedLength) {
37+
throw new Exception('Error: Could not write the entire data to shared memory with length: ' .
38+
$compressedLength . '. Bytes written: ' . $bytesWritten . PHP_EOL);
4039
}
4140

42-
if (PHP_OS_FAMILY === 'Windows') {
43-
$arg = ['start', '""', '/B', PHP_BINARY, __DIR__ . $separator . 'background_processor.php', $this->key];
44-
$process = new SymfonyProcess($arg);
45-
$process->start();
46-
return;
47-
}
48-
49-
exec(PHP_BINARY . ' ' . __DIR__ . $separator . 'background_processor.php ' . $this->key .
50-
' > /dev/null 2>&1 &');
41+
$fileWithPath = __DIR__ . $dirSlash . 'background_processor.php';
42+
$file = new PhpFile($fileWithPath, [$this->shmopKey]);
43+
$file->run();
5144
}
5245
}

src/Terremoth/Async/background_processor.php

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
<?php
22

3+
ini_set('display_errors', 'on');
4+
ini_set('display_startup_errors', 1);
5+
ini_set('error_log', 'php-async-errors-' . date('YmdH') . '.log');
6+
error_reporting(E_ALL);
7+
38
require_once 'vendor/autoload.php';
9+
require_once 'script_functions.php';
410

511
use Laravel\SerializableClosure\SerializableClosure;
612

713
if (!isset($argv[1])) {
8-
fwrite(STDERR, 'Error: Key not provided');
14+
error('Shmop Key not provided');
915
exit(1);
1016
}
1117

1218
$key = (int)$argv[1];
1319

1420
$shmopInstance = shmop_open($key, 'a', 0, 0);
21+
22+
if (!$shmopInstance) {
23+
error('Could not open Shmop');
24+
exit(1);
25+
}
26+
1527
$length = shmop_size($shmopInstance);
16-
$data = shmop_read($shmopInstance, 0, $length);
28+
29+
if ($length === 0) {
30+
error('Shmop length cannot be zero!');
31+
exit(1);
32+
}
33+
34+
$dataCompressed = shmop_read($shmopInstance, 0, $length);
35+
$data = stringFromMemoryBlock($dataCompressed);
1736

1837
/**
1938
* @var SerializableClosure $serializedClosure
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
function error(string $error): void
4+
{
5+
$error = 'Error: ' . $error;
6+
fwrite(STDERR, $error);
7+
error_log($error);
8+
}
9+
10+
function stringFromMemoryBlock(string $value): string
11+
{
12+
$position = strpos($value, "\0");
13+
if ($position === false) {
14+
return $value;
15+
}
16+
return substr($value, 0, $position);
17+
}

0 commit comments

Comments
 (0)