Skip to content

Commit

Permalink
Merge pull request #50 from clue-labs/detect-binary
Browse files Browse the repository at this point in the history
Use default `php` binary instead of respecting `PHP_BINARY` when automatic binary detection fails for non-CLI SAPIs
  • Loading branch information
SimonFrings authored Nov 12, 2021
2 parents e94c9f0 + e7801d9 commit 327320b
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/composer.lock
/examples/users.db
/vendor/
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,33 @@ Let's take these projects to the next level together! 🚀
## Quickstart example

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

```php
<?php

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

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

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

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

$db->quit();
```

See also the [examples](examples).
See also the [examples](examples/).

## Usage

Expand Down
14 changes: 8 additions & 6 deletions examples/insert.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
require __DIR__ . '/../vendor/autoload.php';

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

$n = isset($argv[1]) ? $argv[1] : 1;
$db = $factory->openLazy('test.db');

$promise = $db->exec('CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, bar STRING)');
$promise->then(null, 'printf');
$db->exec(
'CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)'
)->then(null, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$n = isset($argv[1]) ? $argv[1] : 1;
for ($i = 0; $i < $n; ++$i) {
$db->exec("INSERT INTO foo (bar) VALUES ('This is a test')")->then(function (Clue\React\SQLite\Result $result) {
$db->exec("INSERT INTO user (name) VALUES ('Alice')")->then(function (Clue\React\SQLite\Result $result) {
echo 'New row ' . $result->insertId . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
Expand Down
28 changes: 28 additions & 0 deletions examples/query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

// $ php examples/query.php "INSERT INTO user (name) VALUES ('Bob'),('Carol')"
// $ php examples/query.php "DELETE FROM user WHERE name = ?" "Carol"

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

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

$query = isset($argv[1]) ? $argv[1] : 'SELECT 42 AS value';
$args = array_slice(isset($argv) ? $argv : [], 2);

$db->query($query, $args)->then(function (Clue\React\SQLite\Result $result) {
if ($result->columns !== null) {
echo implode("\t", $result->columns) . PHP_EOL;
foreach ($result->rows as $row) {
echo implode("\t", $row) . PHP_EOL;
}
} else {
echo "changed\tid". PHP_EOL;
echo $result->changed . "\t" . $result->insertId . PHP_EOL;
}
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$db->quit();
6 changes: 3 additions & 3 deletions examples/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
require __DIR__ . '/../vendor/autoload.php';

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

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

$db->query('SELECT * FROM foo WHERE bar LIKE ?', ['%' . $search . '%'])->then(function (Clue\React\SQLite\Result $result) {
$db->query('SELECT * FROM user WHERE name LIKE ?', ['%' . $search . '%'])->then(function (Clue\React\SQLite\Result $result) {
echo 'Found ' . count($result->rows) . ' rows: ' . PHP_EOL;
echo implode("\t", $result->columns) . PHP_EOL;
foreach ($result->rows as $row) {
Expand Down
27 changes: 17 additions & 10 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,12 @@ private function openProcessIo($filename, $flags = null)
\defined('STDERR') ? \STDERR : \fopen('php://stderr', 'w')
);

// do not inherit open FDs by explicitly overwriting existing FDs with dummy files
// do not inherit open FDs by explicitly overwriting existing FDs with dummy files.
// 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.
// additionally, close all dummy files in the child process again
foreach ($fds as $fd) {
if ($fd > 2) {
$pipes[$fd] = array('file', '/dev/null', 'r');
$pipes[$fd] = \PHP_VERSION_ID >= 70400 ? ['null'] : $pipes[2];
$command .= ' ' . $fd . '>&-';
}
}
Expand Down Expand Up @@ -375,7 +376,7 @@ private function openSocketIo($filename, $flags = null)
private function which($bin)
{
foreach (\explode(\PATH_SEPARATOR, \getenv('PATH')) as $path) {
if (\is_executable($path . \DIRECTORY_SEPARATOR . $bin)) {
if (@\is_executable($path . \DIRECTORY_SEPARATOR . $bin)) {
return $path . \DIRECTORY_SEPARATOR . $bin;
}
}
Expand All @@ -396,20 +397,26 @@ private function resolve($filename)

/**
* @return string
* @codeCoverageIgnore Covered by `/tests/FunctionalExampleTest.php` instead.
*/
private function php()
{
// if this is the php-cgi binary, check if we can execute the php binary instead
$binary = \PHP_BINARY;
$candidate = \str_replace('-cgi', '', $binary);
if ($candidate !== $binary && \is_executable($candidate)) {
$binary = $candidate; // @codeCoverageIgnore
$binary = 'php';
if (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') {
// use same PHP_BINARY in CLI mode, but do not use same binary for CGI/FPM
$binary = \PHP_BINARY;
} else {
// if this is the php-cgi binary, check if we can execute the php binary instead
$candidate = \str_replace('-cgi', '', \PHP_BINARY);
if ($candidate !== \PHP_BINARY && @\is_executable($candidate)) {
$binary = $candidate;
}
}

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

return $binary;
Expand Down
78 changes: 78 additions & 0 deletions tests/FunctionalExampleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Clue\Tests\React\SQLite;

use PHPUnit\Framework\TestCase;

class FunctionalExampleTest extends TestCase
{
public function testQueryExampleReturnsDefaultValue()
{
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' query.php');

$this->assertEquals('value' . "\n" . '42' . "\n", $output);
}

public function testQueryExampleReturnsCalculatedValueFromPlaceholderVariables()
{
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' query.php "SELECT ?+? AS result" 1 2');

$this->assertEquals('result' . "\n" . '3' . "\n", $output);
}

public function testQueryExampleExecutedWithCgiReturnsDefaultValueAfterContentTypeHeader()
{
if (!$this->canExecute('php-cgi --version')) {
$this->markTestSkipped('Unable to execute "php-cgi"');
}

$output = $this->execExample('php-cgi query.php');

$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
}

public function testQueryExampleWithOpenBasedirRestrictedReturnsDefaultValue()
{
$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');

$this->assertEquals('value' . "\n" . '42' . "\n", $output);
}

public function testQueryExampleWithOpenBasedirRestrictedAndAdditionalFileDescriptorReturnsDefaultValue()
{
if (DIRECTORY_SEPARATOR === '\\') {
$this->markTestSkipped('Not supported on Windows');
}

$output = $this->execExample(escapeshellarg(PHP_BINARY) . ' -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php 3</dev/null');

$this->assertEquals('value' . "\n" . '42' . "\n", $output);
}

public function testQueryExampleExecutedWithCgiAndOpenBasedirRestrictedRunsDefaultPhpAndReturnsDefaultValueAfterContentTypeHeader()
{
if (!$this->canExecute('php-cgi --version') || !$this->canExecute('php --version')) {
$this->markTestSkipped('Unable to execute "php-cgi" or "php"');
}

$output = $this->execExample('php-cgi -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');

$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
}

private function canExecute($command)
{
$code = 1;
$null = DIRECTORY_SEPARATOR === '\\' ? 'NUL' : '/dev/null';
system("$command >$null 2>$null", $code);

return $code === 0;
}

private function execExample($command)
{
chdir(__DIR__ . '/../examples/');

return str_replace("\r\n", "\n", shell_exec($command));
}
}

0 comments on commit 327320b

Please sign in to comment.