Skip to content

Commit 327320b

Browse files
authored
Merge pull request #50 from clue-labs/detect-binary
Use default `php` binary instead of respecting `PHP_BINARY` when automatic binary detection fails for non-CLI SAPIs
2 parents e94c9f0 + e7801d9 commit 327320b

File tree

7 files changed

+144
-25
lines changed

7 files changed

+144
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/composer.lock
2+
/examples/users.db
23
/vendor/

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,33 @@ Let's take these projects to the next level together! 🚀
6060
## Quickstart example
6161

6262
The following example code demonstrates how this library can be used to open an
63-
existing SQLite database file (or automatically create it on first run) and then
64-
`INSERT` a new record to the database:
63+
existing SQLite database file (or automatically create it on the first run) and
64+
then `INSERT` a new record to the database:
6565

6666
```php
6767
<?php
6868

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

7171
$factory = new Clue\React\SQLite\Factory();
72+
$db = $factory->openLazy(__DIR__ . '/users.db');
7273

73-
$db = $factory->openLazy('users.db');
74-
$db->exec('CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, bar STRING)');
74+
$db->exec('CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)');
7575

7676
$name = 'Alice';
77-
$db->query('INSERT INTO foo (bar) VALUES (?)', [$name])->then(
77+
$db->query('INSERT INTO user (name) VALUES (?)', [$name])->then(
7878
function (Clue\React\SQLite\Result $result) use ($name) {
7979
echo 'New ID for ' . $name . ': ' . $result->insertId . PHP_EOL;
80+
},
81+
function (Exception $e) {
82+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
8083
}
8184
);
8285

8386
$db->quit();
8487
```
8588

86-
See also the [examples](examples).
89+
See also the [examples](examples/).
8790

8891
## Usage
8992

examples/insert.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
require __DIR__ . '/../vendor/autoload.php';
44

55
$factory = new Clue\React\SQLite\Factory();
6+
$db = $factory->openLazy(__DIR__ . '/users.db');
67

7-
$n = isset($argv[1]) ? $argv[1] : 1;
8-
$db = $factory->openLazy('test.db');
9-
10-
$promise = $db->exec('CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, bar STRING)');
11-
$promise->then(null, 'printf');
8+
$db->exec(
9+
'CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)'
10+
)->then(null, function (Exception $e) {
11+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
12+
});
1213

14+
$n = isset($argv[1]) ? $argv[1] : 1;
1315
for ($i = 0; $i < $n; ++$i) {
14-
$db->exec("INSERT INTO foo (bar) VALUES ('This is a test')")->then(function (Clue\React\SQLite\Result $result) {
16+
$db->exec("INSERT INTO user (name) VALUES ('Alice')")->then(function (Clue\React\SQLite\Result $result) {
1517
echo 'New row ' . $result->insertId . PHP_EOL;
1618
}, function (Exception $e) {
1719
echo 'Error: ' . $e->getMessage() . PHP_EOL;

examples/query.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
// $ php examples/query.php "INSERT INTO user (name) VALUES ('Bob'),('Carol')"
4+
// $ php examples/query.php "DELETE FROM user WHERE name = ?" "Carol"
5+
6+
require __DIR__ . '/../vendor/autoload.php';
7+
8+
$factory = new Clue\React\SQLite\Factory();
9+
$db = $factory->openLazy(__DIR__ . '/users.db');
10+
11+
$query = isset($argv[1]) ? $argv[1] : 'SELECT 42 AS value';
12+
$args = array_slice(isset($argv) ? $argv : [], 2);
13+
14+
$db->query($query, $args)->then(function (Clue\React\SQLite\Result $result) {
15+
if ($result->columns !== null) {
16+
echo implode("\t", $result->columns) . PHP_EOL;
17+
foreach ($result->rows as $row) {
18+
echo implode("\t", $row) . PHP_EOL;
19+
}
20+
} else {
21+
echo "changed\tid". PHP_EOL;
22+
echo $result->changed . "\t" . $result->insertId . PHP_EOL;
23+
}
24+
}, function (Exception $e) {
25+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
26+
});
27+
28+
$db->quit();

examples/search.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
require __DIR__ . '/../vendor/autoload.php';
44

55
$factory = new Clue\React\SQLite\Factory();
6+
$db = $factory->openLazy(__DIR__ . '/users.db');
67

7-
$search = isset($argv[1]) ? $argv[1] : 'foo';
8-
$db = $factory->openLazy('test.db');
8+
$search = isset($argv[1]) ? $argv[1] : '';
99

10-
$db->query('SELECT * FROM foo WHERE bar LIKE ?', ['%' . $search . '%'])->then(function (Clue\React\SQLite\Result $result) {
10+
$db->query('SELECT * FROM user WHERE name LIKE ?', ['%' . $search . '%'])->then(function (Clue\React\SQLite\Result $result) {
1111
echo 'Found ' . count($result->rows) . ' rows: ' . PHP_EOL;
1212
echo implode("\t", $result->columns) . PHP_EOL;
1313
foreach ($result->rows as $row) {

src/Factory.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,12 @@ private function openProcessIo($filename, $flags = null)
254254
\defined('STDERR') ? \STDERR : \fopen('php://stderr', 'w')
255255
);
256256

257-
// do not inherit open FDs by explicitly overwriting existing FDs with dummy files
257+
// do not inherit open FDs by explicitly overwriting existing FDs with dummy files.
258+
// Accessing /dev/null with null spec requires PHP 7.4+, older PHP versions may be restricted due to open_basedir, so let's reuse STDERR here.
258259
// additionally, close all dummy files in the child process again
259260
foreach ($fds as $fd) {
260261
if ($fd > 2) {
261-
$pipes[$fd] = array('file', '/dev/null', 'r');
262+
$pipes[$fd] = \PHP_VERSION_ID >= 70400 ? ['null'] : $pipes[2];
262263
$command .= ' ' . $fd . '>&-';
263264
}
264265
}
@@ -375,7 +376,7 @@ private function openSocketIo($filename, $flags = null)
375376
private function which($bin)
376377
{
377378
foreach (\explode(\PATH_SEPARATOR, \getenv('PATH')) as $path) {
378-
if (\is_executable($path . \DIRECTORY_SEPARATOR . $bin)) {
379+
if (@\is_executable($path . \DIRECTORY_SEPARATOR . $bin)) {
379380
return $path . \DIRECTORY_SEPARATOR . $bin;
380381
}
381382
}
@@ -396,20 +397,26 @@ private function resolve($filename)
396397

397398
/**
398399
* @return string
400+
* @codeCoverageIgnore Covered by `/tests/FunctionalExampleTest.php` instead.
399401
*/
400402
private function php()
401403
{
402-
// if this is the php-cgi binary, check if we can execute the php binary instead
403-
$binary = \PHP_BINARY;
404-
$candidate = \str_replace('-cgi', '', $binary);
405-
if ($candidate !== $binary && \is_executable($candidate)) {
406-
$binary = $candidate; // @codeCoverageIgnore
404+
$binary = 'php';
405+
if (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') {
406+
// use same PHP_BINARY in CLI mode, but do not use same binary for CGI/FPM
407+
$binary = \PHP_BINARY;
408+
} else {
409+
// if this is the php-cgi binary, check if we can execute the php binary instead
410+
$candidate = \str_replace('-cgi', '', \PHP_BINARY);
411+
if ($candidate !== \PHP_BINARY && @\is_executable($candidate)) {
412+
$binary = $candidate;
413+
}
407414
}
408415

409416
// if `php` is a symlink to the php binary, use the shorter `php` name
410417
// this is purely cosmetic feature for the process list
411-
if (\realpath($this->which('php')) === $binary) {
412-
$binary = 'php'; // @codeCoverageIgnore
418+
if ($binary !== 'php' && \realpath($this->which('php')) === $binary) {
419+
$binary = 'php';
413420
}
414421

415422
return $binary;

tests/FunctionalExampleTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Clue\Tests\React\SQLite;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
class FunctionalExampleTest extends TestCase
8+
{
9+
public function testQueryExampleReturnsDefaultValue()
10+
{
11+
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' query.php');
12+
13+
$this->assertEquals('value' . "\n" . '42' . "\n", $output);
14+
}
15+
16+
public function testQueryExampleReturnsCalculatedValueFromPlaceholderVariables()
17+
{
18+
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' query.php "SELECT ?+? AS result" 1 2');
19+
20+
$this->assertEquals('result' . "\n" . '3' . "\n", $output);
21+
}
22+
23+
public function testQueryExampleExecutedWithCgiReturnsDefaultValueAfterContentTypeHeader()
24+
{
25+
if (!$this->canExecute('php-cgi --version')) {
26+
$this->markTestSkipped('Unable to execute "php-cgi"');
27+
}
28+
29+
$output = $this->execExample('php-cgi query.php');
30+
31+
$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
32+
}
33+
34+
public function testQueryExampleWithOpenBasedirRestrictedReturnsDefaultValue()
35+
{
36+
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');
37+
38+
$this->assertEquals('value' . "\n" . '42' . "\n", $output);
39+
}
40+
41+
public function testQueryExampleWithOpenBasedirRestrictedAndAdditionalFileDescriptorReturnsDefaultValue()
42+
{
43+
if (DIRECTORY_SEPARATOR === '\\') {
44+
$this->markTestSkipped('Not supported on Windows');
45+
}
46+
47+
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php 3</dev/null');
48+
49+
$this->assertEquals('value' . "\n" . '42' . "\n", $output);
50+
}
51+
52+
public function testQueryExampleExecutedWithCgiAndOpenBasedirRestrictedRunsDefaultPhpAndReturnsDefaultValueAfterContentTypeHeader()
53+
{
54+
if (!$this->canExecute('php-cgi --version') || !$this->canExecute('php --version')) {
55+
$this->markTestSkipped('Unable to execute "php-cgi" or "php"');
56+
}
57+
58+
$output = $this->execExample('php-cgi -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');
59+
60+
$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
61+
}
62+
63+
private function canExecute($command)
64+
{
65+
$code = 1;
66+
$null = DIRECTORY_SEPARATOR === '\\' ? 'NUL' : '/dev/null';
67+
system("$command >$null 2>$null", $code);
68+
69+
return $code === 0;
70+
}
71+
72+
private function execExample($command)
73+
{
74+
chdir(__DIR__ . '/../examples/');
75+
76+
return str_replace("\r\n", "\n", shell_exec($command));
77+
}
78+
}

0 commit comments

Comments
 (0)