diff --git a/.travis.yml b/.travis.yml index b192f50..b83a806 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ cache: - $HOME/.composer/cache/files php: - - 7.0 - 7.1 - 7.2 + - 7.3 - nightly before_install: diff --git a/composer.json b/composer.json index aa5f810..b55aeff 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,16 @@ } }, "require": { - "php": ">=5.5.0", + "php": ">=7.1.0", "nacmartin/phpexecjs": "^2.0", + "psr/cache": "^1.0.0", + "psr/log": "^1.1.0", "twig/twig": "^1.20|^2.0" }, "require-dev": { "squizlabs/php_codesniffer": "^2.5", "escapestudios/symfony2-coding-standard": "^2.9", + "phpunit/phpunit": "^7.5.0", "wimg/php-compatibility": "^7.0" }, "scripts": { diff --git a/src/Limenius/ReactRenderer/Renderer/ExternalServerReactRenderer.php b/src/Limenius/ReactRenderer/Renderer/ExternalServerReactRenderer.php index b2909fc..2728bde 100644 --- a/src/Limenius/ReactRenderer/Renderer/ExternalServerReactRenderer.php +++ b/src/Limenius/ReactRenderer/Renderer/ExternalServerReactRenderer.php @@ -4,6 +4,7 @@ use Limenius\ReactRenderer\Context\ContextProviderInterface; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; /** * Class ExternalServerReactRenderer @@ -55,23 +56,43 @@ public function setServerSocketPath($serverSocketPath) */ public function render($componentName, $propsString, $uuid, $registeredStores = array(), $trace) { - if (strpos($this->serverSocketPath, '://') === false) { - $this->serverSocketPath = 'unix://'.$this->serverSocketPath; - } + try { + if (\strpos($this->serverSocketPath, '://') === false) { + $this->serverSocketPath = 'unix://'.$this->serverSocketPath; + } - if (!$sock = stream_socket_client($this->serverSocketPath, $errno, $errstr)) { - throw new \RuntimeException($errstr); - } - stream_socket_sendto($sock, $this->wrap($componentName, $propsString, $uuid, $registeredStores, $trace)."\0"); + if (!$sock = \stream_socket_client($this->serverSocketPath, $errno, $errstr)) { + throw new \RuntimeException($errstr); + } + \stream_socket_sendto( + $sock, + $this->wrap($componentName, $propsString, $uuid, $registeredStores, $trace)."\0" + ); + + $contents = ''; - $contents = ''; + while (!\feof($sock)) { + $contents .= \fread($sock, 8192); + } + \fclose($sock); + + $result = \json_decode($contents, true); + } catch (\Throwable $t) { + if ($this->failLoud) { + throw $t; + } + + if ($this->logger) { + $this->logger->log(LogLevel::ERROR, $t->getMessage(), ['exception' => $t]); + } - while (!feof($sock)) { - $contents .= fread($sock, 8192); + return [ + 'evaluated' => '', + 'consoleReplay' => '', + 'hasErrors' => true, + ]; } - fclose($sock); - $result = json_decode($contents, true); if ($result['hasErrors']) { $this->logErrors($result['consoleReplayScript']); if ($this->failLoud) { diff --git a/src/Limenius/ReactRenderer/Renderer/PhpExecJsReactRenderer.php b/src/Limenius/ReactRenderer/Renderer/PhpExecJsReactRenderer.php index 32b0c5c..73d4415 100644 --- a/src/Limenius/ReactRenderer/Renderer/PhpExecJsReactRenderer.php +++ b/src/Limenius/ReactRenderer/Renderer/PhpExecJsReactRenderer.php @@ -2,10 +2,11 @@ namespace Limenius\ReactRenderer\Renderer; -use Nacmartin\PhpExecJs\PhpExecJs; -use Psr\Log\LoggerInterface; use Limenius\ReactRenderer\Context\ContextProviderInterface; +use Nacmartin\PhpExecJs\PhpExecJs; use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; /** * Class PhpExecJsReactRenderer @@ -93,15 +94,38 @@ public function setServerBundlePath($serverBundlePath) */ public function render($componentName, $propsString, $uuid, $registeredStores = array(), $trace) { - $this->ensurePhpExecJsIsBuilt(); - if ($this->needToSetContext) { - if ($this->phpExecJs->supportsCache()) { - $this->phpExecJs->setCache($this->cache); + try { + $this->ensurePhpExecJsIsBuilt(); + if ($this->needToSetContext) { + if ($this->phpExecJs->supportsCache()) { + $this->phpExecJs->setCache($this->cache); + } + $this->phpExecJs->createContext( + $this->consolePolyfill()."\n".$this->timerPolyfills($trace)."\n".$this->loadServerBundle(), + $this->cacheKey + ); + $this->needToSetContext = false; + } + $result = \json_decode( + $this->phpExecJs->evalJs($this->wrap($componentName, $propsString, $uuid, $registeredStores, $trace)), + true + ); + } catch (\Throwable $t) { + if ($this->failLoud) { + throw $t; + } + + if ($this->logger) { + $this->logger->log(LogLevel::ERROR, $t->getMessage(), ['exception' => $t]); } - $this->phpExecJs->createContext($this->consolePolyfill()."\n".$this->timerPolyfills($trace)."\n".$this->loadServerBundle(), $this->cacheKey); - $this->needToSetContext = false; + + return [ + 'evaluated' => '', + 'consoleReplay' => '', + 'hasErrors' => true, + ]; } - $result = json_decode($this->phpExecJs->evalJs($this->wrap($componentName, $propsString, $uuid, $registeredStores, $trace)), true); + if ($result['hasErrors']) { $this->logErrors($result['consoleReplayScript']); if ($this->failLoud) { diff --git a/tests/Limenius/ReactRenderer/Tests/Renderer/PhpExecJsReactRendererTest.php b/tests/Limenius/ReactRenderer/Tests/Renderer/PhpExecJsReactRendererTest.php index 2deb148..91c4adb 100644 --- a/tests/Limenius/ReactRenderer/Tests/Renderer/PhpExecJsReactRendererTest.php +++ b/tests/Limenius/ReactRenderer/Tests/Renderer/PhpExecJsReactRendererTest.php @@ -2,11 +2,14 @@ namespace Limenius\ReactRenderer\Tests\Renderer; -use Limenius\ReactRenderer\Renderer\PhpExecJsReactRenderer; use Limenius\ReactRenderer\Context\ContextProviderInterface; -use Psr\Log\LoggerInterface; +use Limenius\ReactRenderer\Exception\EvalJsException; +use Limenius\ReactRenderer\Renderer\PhpExecJsReactRenderer; use Nacmartin\PhpExecJs\PhpExecJs; +use PHPUnit\Framework\Exception; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; /** * Class PhpExecJsReactRendererTest @@ -31,7 +34,7 @@ class PhpExecJsReactRendererTest extends TestCase /** * {@inheritdoc} */ - public function setUp() + public function setUp(): void { $this->logger = $this->getMockBuilder(LoggerInterface::class) ->getMock(); @@ -46,11 +49,9 @@ public function setUp() $this->renderer->setPhpExecJs($this->phpExecJs); } - /** - * @expectedException \RuntimeException - */ public function testServerBundleNotFound() { + $this->expectException(\RuntimeException::class); $this->renderer = new PhpExecJsReactRenderer(__DIR__.'/Fixtures/i-dont-exist.js', $this->logger, $this->contextProvider); $this->renderer->render('MyApp', 'props', 1, null, false); } @@ -108,9 +109,6 @@ public function testReactOnRails() $this->renderer->render('MyApp', '{msg:"It Works!"}', 1, null, true)); } - /** - * @expectedException \Limenius\ReactRenderer\Exception\EvalJsException - */ public function testFailLoud() { $phpExecJs = $this->getMockBuilder(PhpExecJs::class) @@ -119,6 +117,54 @@ public function testFailLoud() ->willReturn('{ "html" : "go for it", "hasErrors" : true, "consoleReplayScript": " - my replay"}'); $this->renderer = new PhpExecJsReactRenderer(__DIR__.'/Fixtures/server-bundle.js', true, $this->contextProvider, $this->logger); $this->renderer->setPhpExecJs($phpExecJs); + $this->expectException(EvalJsException::class); + $this->renderer->render('MyApp', 'props', 1, null, true); + } + + /** + * @testdox failLoud true bubbles thrown exceptions + */ + public function testFailLoudBubblesThrownException() + { + $err = new Exception('test exception'); + $this->phpExecJs->method('createContext')->willThrowException($err); + $this->renderer = new PhpExecJsReactRenderer(__DIR__.'/Fixtures/server-bundle.js', true, $this->contextProvider, $this->logger); + $this->renderer->setPhpExecJs($this->phpExecJs); + + $this->expectExceptionObject($err); + $this->renderer->render('MyApp', 'props', 1, null, true); + } + + /** + * @testdox failLoud false returns empty error result on exception + */ + public function testFailQuietReturnsEmptyErrorResultOnException() + { + $this->phpExecJs->method('createContext')->willThrowException(new \Exception('test exception')); + + $this->assertEquals( + [ + 'evaluated' => '', + 'consoleReplay' => '', + 'hasErrors' => true, + ], + $this->renderer->render('MyApp', 'props', 1, null, true) + ); + } + + /** + * @testdox failLoud false logs thrown exceptions + */ + public function testFailQuietLogsThrownExceptions() + { + $err = new Exception('test exception'); + $this->phpExecJs->method('createContext')->willThrowException($err); + + $this->logger + ->expects($this->exactly(1)) + ->method('log') + ->with(LogLevel::ERROR, 'test exception', ['exception' => $err]); + $this->renderer->render('MyApp', 'props', 1, null, true); } }