diff --git a/README.md b/README.md index 995ce88..e832977 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,18 @@ here in order to use the [default loop](https://github.com/reactphp/event-loop#l This value SHOULD NOT be given unless you're sure you want to explicitly use a given event loop instance. +This class takes an optional `?string $binary` parameter that can be used to +pass a custom PHP binary to use when spawning a child process. You can use a +`null` value here in order to automatically detect the current PHP binary. You +may want to pass a custom executable path if this automatic detection fails or +if you explicitly want to run the child process with a different PHP version or +environment than your parent process. + +```php +// advanced usage: pass custom PHP binary to use when spawning child process +$factory = new Clue\React\SQLite\Factory(null, '/usr/bin/php6.0'); +``` + #### open() The `open(string $filename, int $flags = null): PromiseInterface` method can be used to diff --git a/src/Factory.php b/src/Factory.php index b6efccd..d1e4cf1 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -15,7 +15,10 @@ class Factory /** @var LoopInterface */ private $loop; - private $bin = PHP_BINARY; + /** @var string */ + private $bin; + + /** @var bool */ private $useSocket; /** @@ -31,26 +34,28 @@ class Factory * This value SHOULD NOT be given unless you're sure you want to explicitly use a * given event loop instance. * + * This class takes an optional `?string $binary` parameter that can be used to + * pass a custom PHP binary to use when spawning a child process. You can use a + * `null` value here in order to automatically detect the current PHP binary. You + * may want to pass a custom executable path if this automatic detection fails or + * if you explicitly want to run the child process with a different PHP version or + * environment than your parent process. + * + * ```php + * // advanced usage: pass custom PHP binary to use when spawning child process + * $factory = new Clue\React\SQLite\Factory(null, '/usr/bin/php6.0'); + * ``` + * * @param ?LoopInterface $loop + * @param ?string $binary */ - public function __construct(LoopInterface $loop = null) + public function __construct(LoopInterface $loop = null, $binary = null) { $this->loop = $loop ?: Loop::get(); + $this->bin = $binary === null ? $this->php() : $binary; // use socket I/O for Windows only, use faster process pipes everywhere else - $this->useSocket = DIRECTORY_SEPARATOR === '\\'; - - // if this is the php-cgi binary, check if we can execute the php binary instead - $candidate = \str_replace('-cgi', '', $this->bin); - if ($candidate !== $this->bin && \is_executable($candidate)) { - $this->bin = $candidate; // @codeCoverageIgnore - } - - // 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')) === $this->bin) { - $this->bin = 'php'; // @codeCoverageIgnore - } + $this->useSocket = \DIRECTORY_SEPARATOR === '\\'; } /** @@ -360,4 +365,25 @@ private function resolve($filename) } return $filename; } + + /** + * @return string + */ + 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 + } + + // 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 + } + + return $binary; + } } diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index d03f7bf..03dae31 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -7,6 +7,31 @@ class FactoryTest extends TestCase { + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $factory = new Factory(); + + $ref = new \ReflectionProperty($factory, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($factory); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + + public function testConstructWitLoopAndBinaryAssignsBothVariables() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $factory = new Factory($loop, 'php6.0'); + + $ref = new \ReflectionProperty($factory, 'loop'); + $ref->setAccessible(true); + $this->assertSame($loop, $ref->getValue($factory)); + + $ref = new \ReflectionProperty($factory, 'bin'); + $ref->setAccessible(true); + $this->assertSame('php6.0', $ref->getValue($factory)); + } + public function testLoadLazyReturnsDatabaseImmediately() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); diff --git a/tests/FunctionalFactoryTest.php b/tests/FunctionalFactoryTest.php new file mode 100644 index 0000000..0a3ee8a --- /dev/null +++ b/tests/FunctionalFactoryTest.php @@ -0,0 +1,88 @@ +open(':memory:'); + + $promise->then(function (DatabaseInterface $db) { + echo 'open.'; + $db->on('close', function () { + echo 'close.'; + }); + + $db->close(); + }, function (\Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + }); + + $this->expectOutputString('open.close.'); + Loop::run(); + } + + public function testOpenReturnsPromiseWhichFulfillsWithConnectionForMemoryPathAndExplicitPhpBinary() + { + $factory = new Factory(null, PHP_BINARY); + $promise = $factory->open(':memory:'); + + $promise->then(function (DatabaseInterface $db) { + echo 'open.'; + $db->on('close', function () { + echo 'close.'; + }); + + $db->close(); + }, function (\Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + }); + + $this->expectOutputString('open.close.'); + Loop::run(); + } + + public function testOpenReturnsPromiseWhichRejectsWithExceptionWhenPathIsInvalid() + { + $factory = new Factory(); + $promise = $factory->open('/dev/foobar'); + + $promise->then(function (DatabaseInterface $db) { + echo 'open.'; + $db->close(); + }, function (\Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + }); + + $this->expectOutputString('Error: Unable to open database: unable to open database file' . PHP_EOL); + Loop::run(); + } + + public function testOpenReturnsPromiseWhichRejectsWithExceptionWhenExplicitPhpBinaryExitsImmediately() + { + $factory = new Factory(null, 'echo'); + + $ref = new \ReflectionProperty($factory, 'useSocket'); + $ref->setAccessible(true); + $ref->setValue($factory, true); + + $promise = $factory->open(':memory:'); + + $promise->then(function (DatabaseInterface $db) { + echo 'open.'; + $db->close(); + }, function (\Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + }); + + $this->expectOutputString('Error: No connection detected' . PHP_EOL); + Loop::run(); + } +}