Skip to content

Commit c4646c3

Browse files
committed
Support passing custom PHP binary as optional argument to Factory
1 parent 578c8e9 commit c4646c3

File tree

4 files changed

+161
-15
lines changed

4 files changed

+161
-15
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ here in order to use the [default loop](https://github.com/reactphp/event-loop#l
101101
This value SHOULD NOT be given unless you're sure you want to explicitly use a
102102
given event loop instance.
103103

104+
This class takes an optional `?string $binary` parameter that can be used to
105+
pass a custom PHP binary to use when spawning a child process. You can use a
106+
`null` value here in order to automatically detect the current PHP binary. You
107+
may want to pass a custom executable path if this automatic detection fails or
108+
if you explicitly want to run the child process with a different PHP version or
109+
environment than your parent process.
110+
111+
```php
112+
// advanced usage: pass custom PHP binary to use when spawning child process
113+
$factory = new Clue\React\SQLite\Factory(null, '/usr/bin/php6.0');
114+
```
115+
104116
#### open()
105117

106118
The `open(string $filename, int $flags = null): PromiseInterface<DatabaseInterface>` method can be used to

src/Factory.php

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ class Factory
1515
/** @var LoopInterface */
1616
private $loop;
1717

18-
private $bin = PHP_BINARY;
18+
/** @var string */
19+
private $bin;
20+
21+
/** @var bool */
1922
private $useSocket;
2023

2124
/**
@@ -31,26 +34,28 @@ class Factory
3134
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
3235
* given event loop instance.
3336
*
37+
* This class takes an optional `?string $binary` parameter that can be used to
38+
* pass a custom PHP binary to use when spawning a child process. You can use a
39+
* `null` value here in order to automatically detect the current PHP binary. You
40+
* may want to pass a custom executable path if this automatic detection fails or
41+
* if you explicitly want to run the child process with a different PHP version or
42+
* environment than your parent process.
43+
*
44+
* ```php
45+
* // advanced usage: pass custom PHP binary to use when spawning child process
46+
* $factory = new Clue\React\SQLite\Factory(null, '/usr/bin/php6.0');
47+
* ```
48+
*
3449
* @param ?LoopInterface $loop
50+
* @param ?string $binary
3551
*/
36-
public function __construct(LoopInterface $loop = null)
52+
public function __construct(LoopInterface $loop = null, $binary = null)
3753
{
3854
$this->loop = $loop ?: Loop::get();
55+
$this->bin = $binary === null ? $this->php() : $binary;
3956

4057
// use socket I/O for Windows only, use faster process pipes everywhere else
41-
$this->useSocket = DIRECTORY_SEPARATOR === '\\';
42-
43-
// if this is the php-cgi binary, check if we can execute the php binary instead
44-
$candidate = \str_replace('-cgi', '', $this->bin);
45-
if ($candidate !== $this->bin && \is_executable($candidate)) {
46-
$this->bin = $candidate; // @codeCoverageIgnore
47-
}
48-
49-
// if `php` is a symlink to the php binary, use the shorter `php` name
50-
// this is purely cosmetic feature for the process list
51-
if (\realpath($this->which('php')) === $this->bin) {
52-
$this->bin = 'php'; // @codeCoverageIgnore
53-
}
58+
$this->useSocket = \DIRECTORY_SEPARATOR === '\\';
5459
}
5560

5661
/**
@@ -360,4 +365,25 @@ private function resolve($filename)
360365
}
361366
return $filename;
362367
}
368+
369+
/**
370+
* @return string
371+
*/
372+
private function php()
373+
{
374+
// if this is the php-cgi binary, check if we can execute the php binary instead
375+
$binary = \PHP_BINARY;
376+
$candidate = \str_replace('-cgi', '', $binary);
377+
if ($candidate !== $binary && \is_executable($candidate)) {
378+
$binary = $candidate; // @codeCoverageIgnore
379+
}
380+
381+
// if `php` is a symlink to the php binary, use the shorter `php` name
382+
// this is purely cosmetic feature for the process list
383+
if (\realpath($this->which('php')) === $binary) {
384+
$binary = 'php'; // @codeCoverageIgnore
385+
}
386+
387+
return $binary;
388+
}
363389
}

tests/FactoryTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@
77

88
class FactoryTest extends TestCase
99
{
10+
public function testConstructWithoutLoopAssignsLoopAutomatically()
11+
{
12+
$factory = new Factory();
13+
14+
$ref = new \ReflectionProperty($factory, 'loop');
15+
$ref->setAccessible(true);
16+
$loop = $ref->getValue($factory);
17+
18+
$this->assertInstanceOf('React\EventLoop\LoopInterface', $loop);
19+
}
20+
21+
public function testConstructWitLoopAndBinaryAssignsBothVariables()
22+
{
23+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
24+
$factory = new Factory($loop, 'php6.0');
25+
26+
$ref = new \ReflectionProperty($factory, 'loop');
27+
$ref->setAccessible(true);
28+
$this->assertSame($loop, $ref->getValue($factory));
29+
30+
$ref = new \ReflectionProperty($factory, 'bin');
31+
$ref->setAccessible(true);
32+
$this->assertSame('php6.0', $ref->getValue($factory));
33+
}
34+
1035
public function testLoadLazyReturnsDatabaseImmediately()
1136
{
1237
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();

tests/FunctionalFactoryTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Clue\Tests\React\SQLite;
4+
5+
use Clue\React\SQLite\DatabaseInterface;
6+
use Clue\React\SQLite\Factory;
7+
use PHPUnit\Framework\TestCase;
8+
use React\EventLoop\Loop;
9+
10+
class FunctionalFactoryTest extends TestCase
11+
{
12+
public function testOpenReturnsPromiseWhichFulfillsWithConnectionForTemporaryPath()
13+
{
14+
$factory = new Factory();
15+
$promise = $factory->open('');
16+
17+
$promise->then(function (DatabaseInterface $db) {
18+
echo 'open.';
19+
$db->on('close', function () {
20+
echo 'close.';
21+
});
22+
23+
$db->close();
24+
}, function (\Exception $e) {
25+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
26+
});
27+
28+
$this->expectOutputString('open.close.');
29+
Loop::run();
30+
}
31+
32+
public function testOpenReturnsPromiseWhichFulfillsWithConnectionForTemporaryPathAndExplicitPhpBinary()
33+
{
34+
$factory = new Factory(null, PHP_BINARY);
35+
$promise = $factory->open('');
36+
37+
$promise->then(function (DatabaseInterface $db) {
38+
echo 'open.';
39+
$db->on('close', function () {
40+
echo 'close.';
41+
});
42+
43+
$db->close();
44+
}, function (\Exception $e) {
45+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
46+
});
47+
48+
$this->expectOutputString('open.close.');
49+
Loop::run();
50+
}
51+
52+
public function testOpenReturnsPromiseWhichRejectsWithExceptionWhenPathIsInvalid()
53+
{
54+
$factory = new Factory();
55+
$promise = $factory->open('/dev/foobar');
56+
57+
$promise->then(function (DatabaseInterface $db) {
58+
echo 'open.';
59+
$db->close();
60+
}, function (\Exception $e) {
61+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
62+
});
63+
64+
$this->expectOutputString("Error: Unable to open database: unable to open database file\n");
65+
Loop::run();
66+
}
67+
68+
public function testOpenReturnsPromiseWhichRejectsWithExceptionWhenExplicitPhpBinaryExits()
69+
{
70+
$factory = new Factory(null, 'echo');
71+
$promise = $factory->open('');
72+
73+
$promise->then(function (DatabaseInterface $db) {
74+
echo 'open.';
75+
$db->close();
76+
}, function (\Exception $e) {
77+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
78+
});
79+
80+
$this->expectOutputString("Error: Database closed\n");
81+
Loop::run();
82+
}
83+
}

0 commit comments

Comments
 (0)