Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
pdo async query...
<?php
// Warning: this script is suspectible to https://bugs.php.net/bug.php?id=80523
// that bug was actually discovered with this class!
class AsyncQuery
{
private $proc;
private $stdout_handle;
private $stderr_handle;
private $ready = false;
public function fetchAll(): array
{
$this->blockUntilReady();
rewind($this->stdout_handle);
$code = stream_get_contents($this->stdout_handle);
// i want to use json_decode instead, but json can't handle binary data like "\x80"
// but var_export() + eval() is both binary safe =/
$ret = unserialize($code);
return $ret;
}
public function blockUntilReady(): void
{
while (! $this->isReady()) {
usleep(100 * 1000); // 100 ms / 0.1 seconds..
}
}
public function isReady(): bool
{
if ($this->ready) {
return true;
}
$status = proc_get_status($this->proc);
if ($status["running"]) {
return false;
}
$exitcode = $status["exitcode"];
rewind($this->stderr_handle);
$stderr = stream_get_contents($this->stderr_handle);
if ($exitcode !== 0 || ! empty($stderr)) {
throw new \RuntimeException("php query error, exitcode: {$exitcode} stderr: {$stderr}");
}
$this->ready = true;
return true;
}
function __construct(string $query, string $pdo_dsn, string $username, string $password)
{
$code = '$db = new PDO(' . var_export($pdo_dsn, true) . "," . var_export($username, true) . ',' . var_export($password, true) . "," . var_export(array(
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
), true) . ");";
$code .= 'echo serialize($db->query(';
if (1 || ctype_print($query)) {
$code .= var_export($query, true);
} else {
$code .= "hex2bin(" . var_export(bin2hex($query), true) . ")";
}
$code .= ")->fetchAll());";
$cmd = 'php -r ' . escapeshellarg($code);
$this->stdout_handle = tmpfile();
$this->stderr_handle = tmpfile();
$descriptorspecs = array(
// we don't use stdin, but if none is created, the child inherit ours, we don't want that.
// so we create a stdin for the child just to close it.
0 => array(
"pipe",
"rb"
),
1 => $this->stdout_handle,
2 => $this->stderr_handle
);
$pipes = [];
$this->proc = proc_open($cmd, $descriptorspecs, $pipes);
fclose($pipes[0]);
unset($pipes[0], $pipes);
}
private $clean = false;
private function cleanup(): void
{
if ($this->clean) {
return;
}
$this->clean = true;
proc_close($this->proc);
fclose($this->stdout_handle);
fclose($this->stderr_handle);
}
function __destruct()
{
$this->cleanup();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment