From e3774d4a95388044205e48f8a6d9cdced3178e9a Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:25:34 +0100 Subject: [PATCH 01/14] Use phpcs with Doctrine Coding standards modified --- .gitignore | 1 + .phpcs.xml.dist | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ composer.json | 3 +- 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 .phpcs.xml.dist diff --git a/.gitignore b/.gitignore index 7166c792c..2f033e3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /tests/Bridge/Symfony/logs /.bref/ /.php_cs.cache +/.phpcs-cache /composer.lock diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist new file mode 100644 index 000000000..99d19223d --- /dev/null +++ b/.phpcs.xml.dist @@ -0,0 +1,145 @@ + + + + + + + + + + + src + tests + tests/Bridge/Symfony/cache + tests/Bridge/Symfony/logs + tests/Bridge/Laravel/bootstrap/cache + + + + + + + 0 + + + + + 0 + + + + + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + + + + + + + + tests/* + + + + + tests/* + + + diff --git a/composer.json b/composer.json index 097c63175..eb958d424 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,7 @@ "friendsofphp/php-cs-fixer": "^2.4", "illuminate/contracts": "^5.0", "illuminate/http": "^5.0", - "laravel/framework": "^5.6" + "laravel/framework": "^5.6", + "doctrine/coding-standard": "^5.0" } } From faf4a953f250a58c23aab475a7e4c84b7dc04dca Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:25:46 +0100 Subject: [PATCH 02/14] Fix coding style --- src/Application.php | 40 +++++------- src/Bridge/Laravel/Application.php | 8 +-- src/Bridge/Laravel/LaravelAdapter.php | 21 +++---- src/Bridge/Psr7/RequestFactory.php | 14 ++--- src/Bridge/Slim/SlimAdapter.php | 11 +--- src/Bridge/Symfony/SymfonyAdapter.php | 16 ++--- src/Cli/InvokeCommand.php | 67 ++++++++++++--------- src/Cli/WelcomeApplication.php | 9 +-- src/Console/Deployer.php | 56 +++++++++-------- src/Filesystem/DirectoryMirror.php | 26 ++++---- src/Http/LambdaResponse.php | 23 +++---- src/Http/WelcomeHandler.php | 7 +-- src/functions.php | 9 +-- template/bref.php | 2 +- tests/Bridge/Laravel/ApplicationTest.php | 3 +- tests/Bridge/Laravel/LaravelAdapterTest.php | 5 +- tests/Bridge/Laravel/config/app.php | 5 +- tests/Bridge/Psr7/RequestFactoryTest.php | 28 ++++----- tests/Bridge/Slim/SlimAdapterTest.php | 3 +- tests/Bridge/Symfony/SymfonyAdapterTest.php | 23 ++++--- tests/Cli/InvokeCommandTest.php | 7 +-- tests/CliTest.php | 3 +- tests/Http/LambdaResponseTest.php | 7 +-- tests/JsHandler/JsHandlerTest.php | 17 ++++-- tests/JsHandler/bref.array-response.php | 2 +- tests/JsHandler/bref.error.php | 2 +- tests/JsHandler/bref.read-event.php | 2 +- tests/JsHandler/bref.stderr.php | 2 +- tests/JsHandler/bref.stdout.php | 2 +- 29 files changed, 188 insertions(+), 232 deletions(-) diff --git a/src/Application.php b/src/Application.php index 1ac589cff..365bab671 100644 --- a/src/Application.php +++ b/src/Application.php @@ -1,5 +1,4 @@ - - */ class Application { /** @@ -27,19 +23,13 @@ class Application private const BREF_DIRECTORY = '/tmp/.bref'; private const OUTPUT_FILE_NAME = self::BREF_DIRECTORY . '/output.json'; - /** - * @var callable - */ + /** @var callable */ private $simpleHandler; - /** - * @var RequestHandlerInterface - */ + /** @var RequestHandlerInterface */ private $httpHandler; - /** - * @var \Symfony\Component\Console\Application - */ + /** @var \Symfony\Component\Console\Application */ private $cliHandler; public function __construct() @@ -58,7 +48,7 @@ public function __construct() * * @param callable $handler This callable takes a $event parameter (array) and must return anything serializable to JSON. */ - public function simpleHandler(callable $handler) : void + public function simpleHandler(callable $handler): void { $this->simpleHandler = $handler; } @@ -69,7 +59,7 @@ public function simpleHandler(callable $handler) : void * The handler must be a PSR-15 request handler, it can be any * framework that is compatible with PSR-15 for example. */ - public function httpHandler(RequestHandlerInterface $handler) : void + public function httpHandler(RequestHandlerInterface $handler): void { $this->httpHandler = $handler; } @@ -84,7 +74,7 @@ public function httpHandler(RequestHandlerInterface $handler) : void * That can also be a Silly (https://github.com/mnapoli/silly) application * since Silly is based on the Symfony Console. */ - public function cliHandler(\Symfony\Component\Console\Application $console) : void + public function cliHandler(\Symfony\Component\Console\Application $console): void { // Necessary to avoid any `exit()` call :) $console->setAutoExit(false); @@ -103,10 +93,10 @@ public function cliHandler(\Symfony\Component\Console\Application $console) : vo * The application will detect how the lambda is being invoked (HTTP, * CLI, direct invocation, etc.) and execute the proper handler. */ - public function run() : void + public function run(): void { - if (!$this->isRunningInAwsLambda()) { - if (php_sapi_name() == "cli") { + if (! $this->isRunningInAwsLambda()) { + if (PHP_SAPI === 'cli') { $this->cliHandler->setAutoExit(true); $this->cliHandler->run(); } else { @@ -145,7 +135,7 @@ public function run() : void $this->writeLambdaOutput($output); } - private function ensureTempDirectoryExists() : void + private function ensureTempDirectoryExists(): void { $filesystem = new Filesystem; if (! $filesystem->exists(self::BREF_DIRECTORY)) { @@ -153,19 +143,19 @@ private function ensureTempDirectoryExists() : void } } - private function readLambdaEvent() : array + private function readLambdaEvent(): array { // The lambda event is passed as JSON by `handler.js` as a CLI argument - global $argv; + $argv = $_SERVER['argv']; return json_decode($argv[1], true) ?: []; } - private function writeLambdaOutput(string $json) : void + private function writeLambdaOutput(string $json): void { file_put_contents(self::OUTPUT_FILE_NAME, $json); } - private function isRunningInAwsLambda() : bool + private function isRunningInAwsLambda(): bool { // LAMBDA_TASK_ROOT is a constant defined by AWS // TODO: use a solution that would work with other hosts? diff --git a/src/Bridge/Laravel/Application.php b/src/Bridge/Laravel/Application.php index 4393b8080..915de48ba 100644 --- a/src/Bridge/Laravel/Application.php +++ b/src/Bridge/Laravel/Application.php @@ -1,5 +1,4 @@ -isArtisanConsole !== null) { @@ -55,7 +55,7 @@ public function runningInConsole() return parent::runningInConsole(); } - public function overrideRunningInConsole(bool $value) + public function overrideRunningInConsole(bool $value): void { $this->isArtisanConsole = $value; } diff --git a/src/Bridge/Laravel/LaravelAdapter.php b/src/Bridge/Laravel/LaravelAdapter.php index 9c2e4106c..991fcab11 100644 --- a/src/Bridge/Laravel/LaravelAdapter.php +++ b/src/Bridge/Laravel/LaravelAdapter.php @@ -1,9 +1,10 @@ - */ class LaravelAdapter implements RequestHandlerInterface { - /** - * @var Kernel - */ + /** @var Kernel */ private $kernel; public function __construct(Kernel $kernel) @@ -27,7 +24,7 @@ public function __construct(Kernel $kernel) $this->kernel = $kernel; } - public function handle(ServerRequestInterface $request) : ResponseInterface + public function handle(ServerRequestInterface $request): ResponseInterface { // Create a Symfony request that will be used by Laravel $httpFoundationFactory = new HttpFoundationFactory; @@ -36,19 +33,17 @@ public function handle(ServerRequestInterface $request) : ResponseInterface // We create Laravel's HTTP request from the Symfony request // We cannot use Symfony's request directly because the Kernel's implementation // expects a `Illuminate\Http\Request` implementation. - $laravelRequest = \Illuminate\Http\Request::createFromBase($symfonyRequest); + $laravelRequest = Request::createFromBase($symfonyRequest); // Laravel does not forward the headers from the Symfony request // we need to do that explicitly :'( $laravelRequest->headers->replace($symfonyRequest->headers->all()); - /** @var \Illuminate\Http\Response $laravelResponse */ + /** @var Response $laravelResponse */ $laravelResponse = $this->kernel->handle($laravelRequest); $this->kernel->terminate($laravelRequest, $laravelResponse); $psr7Factory = new DiactorosFactory; // The Laravel response extends Symfony so this works fine here - $response = $psr7Factory->createResponse($laravelResponse); - - return $response; + return $psr7Factory->createResponse($laravelResponse); } } diff --git a/src/Bridge/Psr7/RequestFactory.php b/src/Bridge/Psr7/RequestFactory.php index 5a69a6c35..09e0ae115 100644 --- a/src/Bridge/Psr7/RequestFactory.php +++ b/src/Bridge/Psr7/RequestFactory.php @@ -1,5 +1,4 @@ - */ class RequestFactory { /** * Create a PSR-7 server request from an AWS Lambda HTTP event. */ - public static function fromLambdaEvent(array $event) : ServerRequestInterface + public static function fromLambdaEvent(array $event): ServerRequestInterface { $method = $event['httpMethod'] ?? 'GET'; $query = []; @@ -63,7 +60,7 @@ public static function fromLambdaEvent(array $event) : ServerRequestInterface if ($contentType === 'application/x-www-form-urlencoded') { parse_str($bodyString, $parsedBody); } else { - $document = new Part("Content-type: $contentType\r\n\r\n".$bodyString); + $document = new Part("Content-type: $contentType\r\n\r\n" . $bodyString); if ($document->isMultiPart()) { $parsedBody = []; @@ -110,7 +107,7 @@ public static function fromLambdaEvent(array $event) : ServerRequestInterface ); } - private static function createBodyStream(string $body) : StreamInterface + private static function createBodyStream(string $body): StreamInterface { $stream = fopen('php://memory', 'r+'); fwrite($stream, $body); @@ -121,9 +118,10 @@ private static function createBodyStream(string $body) : StreamInterface /** * Parse a string key like "files[id_cards][jpg][]" and do $array['files']['id_cards']['jpg'][] = $value + * * @param mixed $value */ - private static function parseKeyAndInsertValueInArray(array &$array, string $key, $value) : void + private static function parseKeyAndInsertValueInArray(array &$array, string $key, $value): void { if (strpos($key, '[') === false) { $array[$key] = $value; diff --git a/src/Bridge/Slim/SlimAdapter.php b/src/Bridge/Slim/SlimAdapter.php index 11e88b448..bbd045331 100644 --- a/src/Bridge/Slim/SlimAdapter.php +++ b/src/Bridge/Slim/SlimAdapter.php @@ -1,5 +1,4 @@ - */ class SlimAdapter implements RequestHandlerInterface { - /** - * @var App - */ + /** @var App */ private $slim; public function __construct(App $slim) @@ -25,7 +20,7 @@ public function __construct(App $slim) $this->slim = $slim; } - public function handle(ServerRequestInterface $request) : ResponseInterface + public function handle(ServerRequestInterface $request): ResponseInterface { $response = $this->slim->getContainer()->get('response'); diff --git a/src/Bridge/Symfony/SymfonyAdapter.php b/src/Bridge/Symfony/SymfonyAdapter.php index a8a43a28d..1e08532c7 100644 --- a/src/Bridge/Symfony/SymfonyAdapter.php +++ b/src/Bridge/Symfony/SymfonyAdapter.php @@ -1,5 +1,4 @@ - */ class SymfonyAdapter implements RequestHandlerInterface { - /** - * @var HttpKernelInterface - */ + /** @var HttpKernelInterface */ private $httpKernel; public function __construct(HttpKernelInterface $httpKernel) @@ -54,9 +49,8 @@ private function loadSessionFromRequest(Request $symfonyRequest): string return ''; } - $this->httpKernel->getContainer()->get('session')->setId( - $sessionId = $symfonyRequest->cookies->get(session_name(), '') - ); + $sessionId = $symfonyRequest->cookies->get(session_name(), ''); + $this->httpKernel->getContainer()->get('session')->setId($sessionId); return $sessionId; } @@ -92,6 +86,6 @@ private function addSessionCookieToResponseIfChanged(?string $requestSessionId, private function hasSessionsDisabled(): bool { - return false === $this->httpKernel->getContainer()->has('session'); + return $this->httpKernel->getContainer()->has('session') === false; } } diff --git a/src/Cli/InvokeCommand.php b/src/Cli/InvokeCommand.php index 1e3dc8faf..a64b899d0 100644 --- a/src/Cli/InvokeCommand.php +++ b/src/Cli/InvokeCommand.php @@ -1,5 +1,4 @@ - */ class InvokeCommand extends Command { - /** - * @var callable - */ + /** @var callable */ private $invokerLocator; public function __construct(callable $invokerLocator) @@ -27,46 +22,64 @@ public function __construct(callable $invokerLocator) parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->setName('bref:invoke') ->setDescription('Invoke the lambda locally when testing it in a development environment.') ->setHelp('This command does NOT run the lambda on a serverless provider. It can be used to test the lambda in a "direct invocation" mode on a development machine.') ->addOption('event', 'e', InputOption::VALUE_REQUIRED, 'Event data as JSON') - ->addOption('path', 'p', InputOption::VALUE_REQUIRED, 'Event data as file') - ; + ->addOption('path', 'p', InputOption::VALUE_REQUIRED, 'Event data as file'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): void { $simpleHandler = ($this->invokerLocator)(); $event = []; - if ($option = $input->getOption('event')) { - if (null === $event = json_decode($option, true)) { - throw new \RuntimeException('The `--event` option provided contains invalid JSON: ' . $option); + $eventOption = $input->getOption('event'); + if ($eventOption) { + $event = json_decode($eventOption, true); + if ($event === null) { + throw new \RuntimeException('The `--event` option provided contains invalid JSON: ' . $eventOption); } } - if ($option = $input->getOption('path')) { - if (!$path = realpath($option)) { - throw new \RuntimeException('The `--path` option is an invalid path: ' . $option); - } - if (!is_readable($path)) { - throw new \RuntimeException('The `--path` option reference an invalid file path or misses permission: ' . $option); - } - if (!$fileContent = file_get_contents($path)) { - throw new \RuntimeException('Unable to get file content:' . $option); - } - if (null === $event = json_decode($fileContent, true)) { - throw new \RuntimeException('The `--path` option provided an file with invalid JSON content: ' . $option); - } + $pathOption = $input->getOption('path'); + if ($pathOption) { + $event = $this->readPathOption($pathOption); } $payload = $simpleHandler($event); $output->writeln(json_encode($payload, JSON_PRETTY_PRINT)); } + + /** + * @return mixed + */ + private function readPathOption(string $option) + { + $path = realpath($option); + if (! $path) { + throw new \RuntimeException('The `--path` option is an invalid path: ' . $option); + } + + if (! is_readable($path)) { + throw new \RuntimeException('The `--path` option reference an invalid file path or misses permission: ' . $option); + } + + $fileContent = file_get_contents($path); + if (! $fileContent) { + throw new \RuntimeException('Unable to get file content:' . $option); + } + + $event = json_decode($fileContent, true); + if ($event === null) { + throw new \RuntimeException('The `--path` option provided an file with invalid JSON content: ' . $option); + } + + return $event; + } } diff --git a/src/Cli/WelcomeApplication.php b/src/Cli/WelcomeApplication.php index 34bde3826..c858b6ff7 100644 --- a/src/Cli/WelcomeApplication.php +++ b/src/Cli/WelcomeApplication.php @@ -1,5 +1,4 @@ - */ class WelcomeApplication extends Application { @@ -17,7 +14,7 @@ public function __construct() { parent::__construct(); - $this->command('hello', function (SymfonyStyle $io) { + $this->command('hello', function (SymfonyStyle $io): void { $io->writeln('Welcome! This CLI application is working but has no commands.'); $io->writeln([ 'Add your own CLI application by registering a Symfony Console application' @@ -31,7 +28,7 @@ public function __construct() /** * Disable the default commands (help and list). */ - protected function getDefaultCommands() + protected function getDefaultCommands(): array { return []; } diff --git a/src/Console/Deployer.php b/src/Console/Deployer.php index 03f070860..73a9c95d1 100644 --- a/src/Console/Deployer.php +++ b/src/Console/Deployer.php @@ -1,5 +1,4 @@ -createProgressBar($io, 7); @@ -46,7 +45,7 @@ public function invoke(SymfonyStyle $io, string $function, ?string $data, bool $ '--raw' => $raw, ]); - $p = join(' ', array_map( + $p = implode(' ', array_map( function ($value, $key) { if ($value === true) { // Support for "flag" arguments @@ -67,23 +66,23 @@ function ($value, $key) { return $process->getOutput(); } - public function deploy(SymfonyStyle $io, bool $dryRun, ?string $stage) : void + public function deploy(SymfonyStyle $io, bool $dryRun, ?string $stage): void { $progress = $this->createProgressBar($io, 8); $this->generateArchive($progress); - if (!$dryRun) { + if (! $dryRun) { $progress->setMessage('Uploading the lambda'); $progress->display(); $serverlessCommand = 'serverless deploy'; - if (null !== $stage) { + if ($stage !== null) { $serverlessCommand .= ' --stage ' . escapeshellarg($stage); } $process = new Process($serverlessCommand, '.bref/output'); $process->setTimeout(null); $completeDeployOutput = ''; - $process->mustRun(function ($type, $buffer) use ($io, $progress, &$completeDeployOutput) { + $process->mustRun(function ($type, $buffer) use ($io, $progress, &$completeDeployOutput): void { $completeDeployOutput .= $buffer; $progress->clear(); $io->writeln($buffer); @@ -109,9 +108,9 @@ public function deploy(SymfonyStyle $io, bool $dryRun, ?string $stage) : void * @param ProgressBar $progress The progress bar will advance of 7 steps. * @throws \Exception */ - private function generateArchive(ProgressBar $progress) : void + private function generateArchive(ProgressBar $progress): void { - if (!$this->fs->exists('serverless.yml') || !$this->fs->exists('bref.php')) { + if (! $this->fs->exists('serverless.yml') || ! $this->fs->exists('bref.php')) { throw new \Exception('The files `bref.php` and `serverless.yml` are required to deploy, run `bref init` to create them'); } @@ -140,7 +139,7 @@ private function generateArchive(ProgressBar $progress) : void $progress->setMessage('Downloading PHP in the `.bref/bin/` directory'); $progress->display(); - if (!$this->fs->exists('.bref/bin/php/php-' . $phpVersion . '.tar.gz')) { + if (! $this->fs->exists('.bref/bin/php/php-' . $phpVersion . '.tar.gz')) { $this->fs->mkdir('.bref/bin/php'); /* * TODO This option allows to customize the PHP binary used. It should be documented @@ -150,7 +149,7 @@ private function generateArchive(ProgressBar $progress) : void */ $defaultUrl = 'https://s3.amazonaws.com/bref-php/bin/php-' . $phpVersion . '.tar.gz'; $url = $projectConfig['php']['url'] ?? $defaultUrl; - (new Process("curl -sSL $url -o .bref/bin/php/php-" . $phpVersion . ".tar.gz")) + (new Process("curl -sSL $url -o .bref/bin/php/php-$phpVersion.tar.gz")) ->setTimeout(null) ->mustRun(); } @@ -159,13 +158,13 @@ private function generateArchive(ProgressBar $progress) : void $progress->setMessage('Installing the PHP binary'); $progress->display(); $this->fs->mkdir('.bref/output/.bref/bin'); - (new Process('tar -xzf .bref/bin/php/php-' . $phpVersion . '.tar.gz -C .bref/output/.bref/bin')) + (new Process("tar -xzf .bref/bin/php/php-$phpVersion.tar.gz -C .bref/output/.bref/bin")) ->mustRun(); // Set correct permissions on the file $this->fs->chmod('.bref/output/.bref/bin', 0755); // Install our custom php.ini and merge it with user configuration $phpConfig = $this->buildPhpConfig( - __DIR__ . '/../../template/php.ini', + __DIR__ . '/../../template/php.ini', '.bref/output/.bref/php.ini', $projectConfig['php']['configuration'] ?? [], $projectConfig['php']['extensions'] ?? [] @@ -200,14 +199,14 @@ private function generateArchive(ProgressBar $progress) : void $progress->advance(); } - private function runLocally(string $command) : void + private function runLocally(string $command): void { $process = new Process($command, '.bref/output'); $process->setTimeout(null); $process->mustRun(); } - private function createProgressBar(SymfonyStyle $io, int $max) : ProgressBar + private function createProgressBar(SymfonyStyle $io, int $max): ProgressBar { ProgressBar::setFormatDefinition('bref', "%message%\n %current%/%max% [%bar%] %elapsed:6s%"); @@ -223,7 +222,7 @@ private function createProgressBar(SymfonyStyle $io, int $max) : ProgressBar /** * Pre-process the `serverless.yml` file and copy it in the lambda directory. */ - private function copyServerlessYml() : void + private function copyServerlessYml(): void { $serverlessYml = Yaml::parse(file_get_contents('serverless.yml')); @@ -234,9 +233,9 @@ private function copyServerlessYml() : void file_put_contents('.bref/output/serverless.yml', Yaml::dump($serverlessYml, 10)); } - private function copyProjectToOutputDirectory() : void + private function copyProjectToOutputDirectory(): void { - if (!$this->fs->exists('.bref/output')) { + if (! $this->fs->exists('.bref/output')) { $this->fs->mkdir('.bref/output'); } @@ -261,14 +260,13 @@ private function copyProjectToOutputDirectory() : void private function buildPhpConfig(string $sourceFile, string $targetFile, array $flags, array $extensions): array { $config = array_merge( - ['flags' => array_merge( - (new IniReader())->readFile($sourceFile), - $flags - )], + [ + 'flags' => array_merge((new IniReader)->readFile($sourceFile), $flags), + ], array_combine( $extensions, array_map(function ($extension) { - if (!$this->fs->exists('.bref/output/.bref/bin/ext/' . $extension . '.so')) { + if (! $this->fs->exists(".bref/output/.bref/bin/ext/$extension.so")) { throw new \Exception("The PHP extension '$extension' is not available yet in Bref, please open an issue or a pull request on GitHub to add that extension"); } @@ -277,24 +275,24 @@ private function buildPhpConfig(string $sourceFile, string $targetFile, array $f ) ); - (new IniWriter())->writeToFile($targetFile, $config); + (new IniWriter)->writeToFile($targetFile, $config); return $config; } - private function removeUnusedExtensions(array $phpConfig) + private function removeUnusedExtensions(array $phpConfig): void { foreach (glob('.bref/output/.bref/bin/ext/*.so') as $extensionFile) { if ($extensionFile === '.bref/output/.bref/bin/ext/opcache.so') { continue; } - if (!array_key_exists(basename($extensionFile, '.so'), $phpConfig)) { + if (! array_key_exists(basename($extensionFile, '.so'), $phpConfig)) { $this->fs->remove($extensionFile); } } } - private function removeUnusedLibraries(array $extensions) + private function removeUnusedLibraries(array $extensions): void { $dependencies = []; $dependenciesFile = '.bref/output/.bref/bin/dependencies.yml'; @@ -309,7 +307,7 @@ private function removeUnusedLibraries(array $extensions) )); foreach (glob('.bref/output/.bref/bin/lib/**') as $library) { - if (!in_array(basename($library), $requiredLibraries)) { + if (! in_array(basename($library), $requiredLibraries)) { $this->fs->remove($library); } } diff --git a/src/Filesystem/DirectoryMirror.php b/src/Filesystem/DirectoryMirror.php index 1988c081d..154882a16 100644 --- a/src/Filesystem/DirectoryMirror.php +++ b/src/Filesystem/DirectoryMirror.php @@ -1,5 +1,4 @@ - + * Mirrors a directory into another on the filesystem. */ class DirectoryMirror { - /** - * @var Filesystem - */ + /** @var Filesystem */ private $fs; public function __construct(Filesystem $fs) @@ -30,14 +27,14 @@ public function __construct(Filesystem $fs) * * @throws IOException */ - public function mirror(Finder $source, Finder $target) : void + public function mirror(Finder $source, Finder $target): void { [$sourceFiles, $targetFiles] = $this->indexArraysByRelativePath($source, $target); $filesToCreate = array_diff_key($sourceFiles, $targetFiles); $filesToDelete = array_diff_key($targetFiles, $sourceFiles); $filesToVerify = array_intersect_key($sourceFiles, $targetFiles); - $filesToUpdate = array_filter($filesToVerify, function (int $sourceCTime, string $sourceRelativePath) use ($targetFiles) : bool { + $filesToUpdate = array_filter($filesToVerify, function (int $sourceCTime, string $sourceRelativePath) use ($targetFiles): bool { assert(isset($targetFiles[$sourceRelativePath])); $targetCTime = $targetFiles[$sourceRelativePath]; /* @@ -59,7 +56,7 @@ public function mirror(Finder $source, Finder $target) : void /** * @param SplFileInfo[] $filesToCreate */ - private function createMissingFiles(array $filesToCreate) : void + private function createMissingFiles(array $filesToCreate): void { foreach ($filesToCreate as $relativePath => $cTime) { $targetPath = '.bref/output/' . $relativePath; @@ -75,7 +72,7 @@ private function createMissingFiles(array $filesToCreate) : void /** * @param SplFileInfo[] $filesToDelete */ - private function deleteExtraFiles(array $filesToDelete) : void + private function deleteExtraFiles(array $filesToDelete): void { foreach ($filesToDelete as $relativePath => $cTime) { $targetPath = '.bref/output/' . $relativePath; @@ -87,7 +84,7 @@ private function deleteExtraFiles(array $filesToDelete) : void /** * @param SplFileInfo[] $filesToUpdate */ - private function updateChangedFiles(array $filesToUpdate) : void + private function updateChangedFiles(array $filesToUpdate): void { foreach ($filesToUpdate as $relativePath => $cTime) { $targetPath = '.bref/output/' . $relativePath; @@ -96,11 +93,10 @@ private function updateChangedFiles(array $filesToUpdate) : void $this->fs->remove($targetPath); $this->fs->copy($relativePath, $targetPath); } else { + // TODO sync permissions if $targetPath is a directory? if (is_file($targetPath)) { $this->fs->remove($targetPath); $this->fs->mkdir($targetPath); - } else { - // TODO sync permissions? } } } @@ -109,7 +105,7 @@ private function updateChangedFiles(array $filesToUpdate) : void /** * @return int[][] */ - private function indexArraysByRelativePath(Finder $source, Finder $target) : array + private function indexArraysByRelativePath(Finder $source, Finder $target): array { $sourceFiles = iterator_to_array($this->indexFilesCTimeByRelativePath($source)); $targetFiles = iterator_to_array($this->indexFilesCTimeByRelativePath($target)); @@ -117,7 +113,7 @@ private function indexArraysByRelativePath(Finder $source, Finder $target) : arr return [$sourceFiles, $targetFiles]; } - private function indexFilesCTimeByRelativePath(iterable $files) : \Traversable + private function indexFilesCTimeByRelativePath(iterable $files): \Traversable { foreach ($files as $file) { /** @var SplFileInfo $file */ diff --git a/src/Http/LambdaResponse.php b/src/Http/LambdaResponse.php index 170b63f83..85107567c 100644 --- a/src/Http/LambdaResponse.php +++ b/src/Http/LambdaResponse.php @@ -1,5 +1,4 @@ - */ class LambdaResponse { - /** - * @var int - */ + /** @var int */ private $statusCode = 200; - /** - * @var array - */ + /** @var array */ private $headers; - /** - * @var string - */ + /** @var string */ private $body; public function __construct(int $statusCode, array $headers, string $body) @@ -34,7 +25,7 @@ public function __construct(int $statusCode, array $headers, string $body) $this->body = $body; } - public static function fromPsr7Response(ResponseInterface $response) : self + public static function fromPsr7Response(ResponseInterface $response): self { // The lambda proxy integration does not support arrays in headers $headers = []; @@ -54,7 +45,7 @@ public static function fromPsr7Response(ResponseInterface $response) : self return new self($response->getStatusCode(), $headers, $body); } - public static function fromHtml(string $html) : self + public static function fromHtml(string $html): self { return new self( 200, @@ -65,7 +56,7 @@ public static function fromHtml(string $html) : self ); } - public function toJson() : string + public function toJson(): string { // The headers must be a JSON object. If the PHP array is empty it is // serialized to `[]` (we want `{}`) so we force it to an empty object. diff --git a/src/Http/WelcomeHandler.php b/src/Http/WelcomeHandler.php index 241375096..abdb7d7be 100644 --- a/src/Http/WelcomeHandler.php +++ b/src/Http/WelcomeHandler.php @@ -1,5 +1,4 @@ - */ class WelcomeHandler implements RequestHandlerInterface { - public function handle(ServerRequestInterface $request) : ResponseInterface + public function handle(ServerRequestInterface $request): ResponseInterface { $html = file_get_contents(__DIR__ . '/welcome.html'); diff --git a/src/functions.php b/src/functions.php index 3f2413455..4dbacd30f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,5 +1,6 @@ -simpleHandler($handler); $app->run(); } diff --git a/template/bref.php b/template/bref.php index c720a1a26..984753c1c 100644 --- a/template/bref.php +++ b/template/bref.php @@ -1,4 +1,4 @@ -getBody()); } - private function createLaravel() : Application + private function createLaravel(): Application { $app = new Application(__DIR__); $app->singleton(Kernel::class, \Illuminate\Foundation\Http\Kernel::class); diff --git a/tests/Bridge/Laravel/config/app.php b/tests/Bridge/Laravel/config/app.php index b62512838..f0b60b479 100644 --- a/tests/Bridge/Laravel/config/app.php +++ b/tests/Bridge/Laravel/config/app.php @@ -1,4 +1,3 @@ - '/test', 'requestTimeEpoch' => $currentTimestamp, ], - 'headers' => [ - ], + 'headers' => [], ]); self::assertEquals('GET', $request->getMethod()); @@ -108,7 +106,7 @@ public function test multipart form data is supported() 'Content-Type' => 'multipart/form-data; boundary=testBoundary', ], 'body' => -"--testBoundary\r + "--testBoundary\r Content-Disposition: form-data; name=\"foo\"\r \r bar\r @@ -134,7 +132,7 @@ public function test cookies are supported() self::assertEquals([ 'tz' => 'Europe/Paris', 'four' => 'two + 2', - 'theme' => 'light' + 'theme' => 'light', ], $request->getCookieParams()); } @@ -151,10 +149,8 @@ public function test arrays in query string are supported() self::assertEquals([ 'vars' => [ 'val1' => 'foo', - 'val2' => [ - 'bar', - ] - ] + 'val2' => ['bar'], + ], ], $request->getQueryParams()); } @@ -166,7 +162,7 @@ public function test arrays in name are supported with multipart form d 'Content-Type' => 'multipart/form-data; boundary=testBoundary', ], 'body' => -"--testBoundary\r + "--testBoundary\r Content-Disposition: form-data; name=\"delete[categories][]\"\r \r 123\r @@ -180,10 +176,10 @@ public function test arrays in name are supported with multipart form d self::assertEquals('POST', $request->getMethod()); self::assertEquals( [ - 'delete' => [ - 'categories' => [ - '123', - '456', + 'delete' => [ + 'categories' => [ + '123', + '456', ], ], ], @@ -199,7 +195,7 @@ public function test files are supported with multipart form data() 'Content-Type' => 'multipart/form-data; boundary=testBoundary', ], 'body' => -"--testBoundary\r + "--testBoundary\r Content-Disposition: form-data; name=\"foo\"; filename=\"lorem.txt\"\r Content-Type: text/plain\r \r diff --git a/tests/Bridge/Slim/SlimAdapterTest.php b/tests/Bridge/Slim/SlimAdapterTest.php index 515037e17..86d0692e1 100644 --- a/tests/Bridge/Slim/SlimAdapterTest.php +++ b/tests/Bridge/Slim/SlimAdapterTest.php @@ -1,5 +1,4 @@ -getContainer()->get('session')->getId(); self::assertEquals($symfonySessionId, (string) $response->getBody()); self::assertEquals( - sprintf("%s=%s; path=/", \session_name(), $symfonySessionId), + sprintf('%s=%s; path=/', \session_name(), $symfonySessionId), $response->getHeaders()['Set-Cookie'][0] ); } @@ -103,42 +102,42 @@ private function createKernel(): HttpKernelInterface $kernel = new class('dev', false) extends Kernel implements EventSubscriberInterface { use MicroKernelTrait; - public function registerBundles() + public function registerBundles(): array { return [new FrameworkBundle]; } - protected function configureContainer(ContainerBuilder $c) + protected function configureContainer(ContainerBuilder $c): void { $c->register('session_storage', MockArraySessionStorage::class); $c->loadFromExtension('framework', [ 'secret' => 'foo', 'session' => [ - 'storage_id' => 'session_storage' - ] + 'storage_id' => 'session_storage', + ], ]); } - protected function configureRoutes(RouteCollectionBuilder $routes) + protected function configureRoutes(RouteCollectionBuilder $routes): void { $routes->add('/', 'kernel:testActionWithoutSession'); $routes->add('/session', 'kernel:testActionWithSession'); } - public function testActionWithoutSession() + public function testActionWithoutSession(): Response { return new Response('Hello world!'); } - public function testActionWithSession(Session $session) + public function testActionWithSession(Session $session): Response { $session->set('ACTIVATE', 'SESSIONS'); // ensure that Symfony starts/uses sessions return new Response($session->getId()); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [KernelEvents::EXCEPTION => 'onKernelException']; } @@ -146,7 +145,7 @@ public static function getSubscribedEvents() /** * We have to handle NotFound exceptions ourselves because they are not handled by the micro-kernel */ - public function onKernelException(GetResponseForExceptionEvent $event) + public function onKernelException(GetResponseForExceptionEvent $event): void { if ($event->getException() instanceof NotFoundHttpException) { $event->setResponse(new Response('Not found', 404)); diff --git a/tests/Cli/InvokeCommandTest.php b/tests/Cli/InvokeCommandTest.php index 56a9bb33a..f027c144d 100644 --- a/tests/Cli/InvokeCommandTest.php +++ b/tests/Cli/InvokeCommandTest.php @@ -1,10 +1,9 @@ -assertJsonPayload($response, [ 'isBase64Encoded' => false, 'statusCode' => 204, - 'headers' => [ - 'Foo' => 'baz', - ], + 'headers' => ['Foo' => 'baz'], 'body' => '', ]); } diff --git a/tests/JsHandler/JsHandlerTest.php b/tests/JsHandler/JsHandlerTest.php index 39ce98573..fe72ff0fa 100644 --- a/tests/JsHandler/JsHandlerTest.php +++ b/tests/JsHandler/JsHandlerTest.php @@ -1,5 +1,4 @@ -setEnv([ @@ -77,7 +76,7 @@ private static function assertProcessResult( string $stdout, string $stderr = '', int $exitCode = 0 - ) { + ): void { $fullOutput = $process->getOutput() . $process->getErrorOutput(); self::assertEquals($exitCode, $process->getExitCode(), $fullOutput); @@ -85,13 +84,19 @@ private static function assertProcessResult( self::assertEquals($stderr, $process->getErrorOutput()); } - private static function assertLambdaResponse($expected) : void + /** + * @param mixed $expected + */ + private static function assertLambdaResponse($expected): void { $response = json_decode(file_get_contents(__DIR__ . '/tmp/testResponse.json'), true); self::assertEquals($expected, $response); } - private static function assertLambdaError($expected) : void + /** + * @param mixed $expected + */ + private static function assertLambdaError($expected): void { $error = json_decode(file_get_contents(__DIR__ . '/tmp/testError.json'), true); self::assertEquals($expected, $error); diff --git a/tests/JsHandler/bref.array-response.php b/tests/JsHandler/bref.array-response.php index 6a7b722c1..b4380034e 100644 --- a/tests/JsHandler/bref.array-response.php +++ b/tests/JsHandler/bref.array-response.php @@ -1,4 +1,4 @@ - Date: Sun, 25 Nov 2018 12:28:29 +0100 Subject: [PATCH 03/14] Remove php-cs-fixer --- .gitignore | 1 - .php_cs | 17 ----------------- composer.json | 1 - 3 files changed, 19 deletions(-) delete mode 100644 .php_cs diff --git a/.gitignore b/.gitignore index 2f033e3a1..c7c94da5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,5 @@ /tests/Bridge/Symfony/cache /tests/Bridge/Symfony/logs /.bref/ -/.php_cs.cache /.phpcs-cache /composer.lock diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 2e0b42b84..000000000 --- a/.php_cs +++ /dev/null @@ -1,17 +0,0 @@ -in(__DIR__) - ->exclude([ - '.bref', - 'vendor', - 'tests/Bridge/Symfony/cache', - 'tests/Bridge/Symfony/logs', - ]); - -return PhpCsFixer\Config::create() - ->setRules([ - '@PSR2' => true, - '@PHP70Migration' => true, - ]) - ->setFinder($finder); diff --git a/composer.json b/composer.json index eb958d424..95f4bd80a 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,6 @@ "symfony/symfony": "^3.4|^4.0", "symfony/debug": "^3.1|^4.0", "slim/slim": "^3.9", - "friendsofphp/php-cs-fixer": "^2.4", "illuminate/contracts": "^5.0", "illuminate/http": "^5.0", "laravel/framework": "^5.6", From ccd6656106b4ee754c7755e2188cb696accf3389 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:33:54 +0100 Subject: [PATCH 04/14] Clarify phpcs config --- .phpcs.xml.dist | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 99d19223d..b90a62cce 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -55,7 +55,7 @@ - + 0 @@ -66,12 +66,12 @@ - + 0 - + @@ -89,18 +89,10 @@ - - - + + + + @@ -111,8 +103,9 @@ - - + + + 0 @@ -124,15 +117,23 @@ 0 - - - - - + + + + 0 + + + 0 + + + 0 + + + + + tests/* From 0a8ae02e92f7455de6d147e293d94d65cab29a3d Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:35:39 +0100 Subject: [PATCH 05/14] Require dependencies on https://prettci.com --- .prettyci.composer.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .prettyci.composer.json diff --git a/.prettyci.composer.json b/.prettyci.composer.json new file mode 100644 index 000000000..5ae6e7a5a --- /dev/null +++ b/.prettyci.composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "doctrine/coding-standard": "^5.0" + } +} From 9ea07192795503c3bbbca89d70e0f5da646ef6c3 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:47:29 +0100 Subject: [PATCH 06/14] Add phpstan --- composer.json | 8 +++++++- phpstan.neon.dist | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index 95f4bd80a..1072ab89d 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,11 @@ "src/functions.php" ] }, + "autoload-dev": { + "psr-4": { + "Bref\\Test\\": "tests" + } + }, "bin": [ "bref" ], @@ -38,6 +43,7 @@ "illuminate/contracts": "^5.0", "illuminate/http": "^5.0", "laravel/framework": "^5.6", - "doctrine/coding-standard": "^5.0" + "doctrine/coding-standard": "^5.0", + "phpstan/phpstan": "^0.10.5" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..1a3b077e4 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +parameters: + level: 1 + paths: + - src + - tests + excludes_analyse: + - %rootDir%/../../../tests/Bridge/Laravel/bootstrap/cache/* + - %rootDir%/../../../tests/Bridge/Laravel/storage/* + - %rootDir%/../../../tests/Bridge/Symfony/cache/* + - %rootDir%/../../../tests/Bridge/Symfony/logs/* From fa1779d0c8b6acb7365b2d6fd190262a637b8d61 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:47:43 +0100 Subject: [PATCH 07/14] Fix for phpstan level 1 --- bref | 2 -- src/Console/Deployer.php | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bref b/bref index 9e6d1bc47..1f8256873 100755 --- a/bref +++ b/bref @@ -8,8 +8,6 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Process\Process; -const DEFAULT_PHP_TARGET_VERSION = '7.2.5'; - if (file_exists(__DIR__ . '/vendor/autoload.php')) { require_once __DIR__ . '/vendor/autoload.php'; } elseif (file_exists(__DIR__ . '/../autoload.php')) { diff --git a/src/Console/Deployer.php b/src/Console/Deployer.php index 73a9c95d1..2d800be39 100644 --- a/src/Console/Deployer.php +++ b/src/Console/Deployer.php @@ -16,6 +16,8 @@ class Deployer { + private const DEFAULT_PHP_TARGET_VERSION = '7.2.5'; + /** @var Filesystem */ private $fs; @@ -135,7 +137,7 @@ private function generateArchive(ProgressBar $progress): void // Cache PHP's binary in `.bref/bin/php` to avoid downloading it // on every deploy. - $phpVersion = $projectConfig['php']['version'] ?? DEFAULT_PHP_TARGET_VERSION; + $phpVersion = $projectConfig['php']['version'] ?? self::DEFAULT_PHP_TARGET_VERSION; $progress->setMessage('Downloading PHP in the `.bref/bin/` directory'); $progress->display(); From b156509685ac21981572dec23dd0896e4ca51447 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:50:02 +0100 Subject: [PATCH 08/14] Run phpstan on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c926d335c..f4d96fbd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,3 +27,4 @@ before_script: script: - vendor/bin/phpunit + - vendor/bin/phpstan analyse From 09bda1a3e19d9cc2a91c3e61af022c30dfa813c0 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 12:53:25 +0100 Subject: [PATCH 09/14] Fix Symfony adapter: it is actually requiring the whole Kernel Previously it was requiring the HTTP Kernel which was a mistake because we use Kernel methods (`getContainer()`). --- src/Bridge/Symfony/SymfonyAdapter.php | 16 +++++++++++----- tests/Bridge/Symfony/SymfonyAdapterTest.php | 10 +++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Bridge/Symfony/SymfonyAdapter.php b/src/Bridge/Symfony/SymfonyAdapter.php index 1e08532c7..d37c3b23b 100644 --- a/src/Bridge/Symfony/SymfonyAdapter.php +++ b/src/Bridge/Symfony/SymfonyAdapter.php @@ -10,7 +10,8 @@ use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\TerminableInterface; /** @@ -18,10 +19,10 @@ */ class SymfonyAdapter implements RequestHandlerInterface { - /** @var HttpKernelInterface */ + /** @var KernelInterface */ private $httpKernel; - public function __construct(HttpKernelInterface $httpKernel) + public function __construct(KernelInterface $httpKernel) { $this->httpKernel = $httpKernel; } @@ -50,7 +51,10 @@ private function loadSessionFromRequest(Request $symfonyRequest): string } $sessionId = $symfonyRequest->cookies->get(session_name(), ''); - $this->httpKernel->getContainer()->get('session')->setId($sessionId); + + /** @var SessionInterface $session */ + $session = $this->httpKernel->getContainer()->get('session'); + $session->setId($sessionId); return $sessionId; } @@ -61,7 +65,9 @@ private function addSessionCookieToResponseIfChanged(?string $requestSessionId, return; } - $responseSessionId = $this->httpKernel->getContainer()->get('session')->getId(); + /** @var SessionInterface $session */ + $session = $this->httpKernel->getContainer()->get('session'); + $responseSessionId = $session->getId(); if ($requestSessionId === $responseSessionId) { return; diff --git a/tests/Bridge/Symfony/SymfonyAdapterTest.php b/tests/Bridge/Symfony/SymfonyAdapterTest.php index a2d8b5c0b..eb01954cd 100644 --- a/tests/Bridge/Symfony/SymfonyAdapterTest.php +++ b/tests/Bridge/Symfony/SymfonyAdapterTest.php @@ -11,12 +11,13 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\RouteCollectionBuilder; use Zend\Diactoros\ServerRequest; @@ -69,7 +70,10 @@ public function test an active session is created when sessions used() $response = $adapter->handle(new ServerRequest([], [], self::ROUTE_WITH_SESSION)); - $symfonySessionId = $kernel->getContainer()->get('session')->getId(); + /** @var SessionInterface $session */ + $session = $kernel->getContainer()->get('session'); + $symfonySessionId = $session->getId(); + self::assertEquals($symfonySessionId, (string) $response->getBody()); self::assertEquals( sprintf('%s=%s; path=/', \session_name(), $symfonySessionId), @@ -97,7 +101,7 @@ public function test an existing session is used when session provided() self::assertEquals('SESSIONID', (string) $response->getBody()); } - private function createKernel(): HttpKernelInterface + private function createKernel(): KernelInterface { $kernel = new class('dev', false) extends Kernel implements EventSubscriberInterface { use MicroKernelTrait; From 39329581f1ef464f0249dba91ca8fda87328a945 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 13:06:49 +0100 Subject: [PATCH 10/14] Simplify test by removing prophecy --- phpstan.neon.dist | 2 +- tests/Cli/InvokeCommandTest.php | 56 ++++++++++++++------------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a3b077e4..26ed6a40d 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 paths: - src - tests diff --git a/tests/Cli/InvokeCommandTest.php b/tests/Cli/InvokeCommandTest.php index f027c144d..ef2ad5697 100644 --- a/tests/Cli/InvokeCommandTest.php +++ b/tests/Cli/InvokeCommandTest.php @@ -4,54 +4,44 @@ use Bref\Cli\InvokeCommand; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\NullOutput; class InvokeCommandTest extends TestCase { - /** @var InputInterface */ - public $inputInterface; - - /** @var OutputInterface */ - public $outputInterface; - /** @var InvokeCommand */ - public static $invokeCommand; - - public const INVALID_JSON = '/'; - public const VALID_JSON = '{}'; + public $command; - public static function setUpBeforeClass() + public function setUp() { - self::$invokeCommand = new InvokeCommand(function () { - return function ($event): void { + parent::setUp(); + + $this->command = new InvokeCommand(function () { + return function (array $event): string { + return 'Hello ' . $event['foo']; }; }); } - public function setUp() - { - $this->inputInterface = $this->prophesize(InputInterface::class); - $this->inputInterface->bind(Argument::any())->willReturn(null); - $this->inputInterface->isInteractive()->willReturn(false); - $this->inputInterface->hasArgument(Argument::any())->willReturn(false); - $this->inputInterface->validate()->willReturn(null); - $this->outputInterface = $this->prophesize(OutputInterface::class); - } - + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage The `--event` option provided contains invalid JSON: {fooo + */ public function test event json_decode fail() { - $this->expectException(\RuntimeException::class); - $this->inputInterface->getOption(Argument::exact('event'))->willReturn(self::INVALID_JSON); - self::$invokeCommand->run($this->inputInterface->reveal(), $this->outputInterface->reveal()); + $this->command->run(new StringInput('--event {fooo'), new NullOutput); } public function test event json_decode success() { - $this->inputInterface->getOption(Argument::exact('event'))->willReturn(self::VALID_JSON); - $this->inputInterface->getOption(Argument::exact('path'))->willReturn(null); - $this->outputInterface->writeln(Argument::any())->shouldBeCalled(1); - self::$invokeCommand->run($this->inputInterface->reveal(), $this->outputInterface->reveal()); + $output = new BufferedOutput; + + $this->command->run(new ArrayInput([ + '--event' => '{"foo": "bar"}', + ]), $output); + + self::assertEquals('"Hello bar"', trim($output->fetch())); } } From a18ce7f3e6d8009be02f04732412ed5fa8d2d8d9 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 13:10:31 +0100 Subject: [PATCH 11/14] Fix phpdoc and upgrade to phpstan level 5 --- phpstan.neon.dist | 2 +- src/Filesystem/DirectoryMirror.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 26ed6a40d..e1608abc4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 2 + level: 5 paths: - src - tests diff --git a/src/Filesystem/DirectoryMirror.php b/src/Filesystem/DirectoryMirror.php index 154882a16..a0bbec3b0 100644 --- a/src/Filesystem/DirectoryMirror.php +++ b/src/Filesystem/DirectoryMirror.php @@ -54,7 +54,7 @@ public function mirror(Finder $source, Finder $target): void } /** - * @param SplFileInfo[] $filesToCreate + * @param int[] $filesToCreate */ private function createMissingFiles(array $filesToCreate): void { @@ -70,7 +70,7 @@ private function createMissingFiles(array $filesToCreate): void } /** - * @param SplFileInfo[] $filesToDelete + * @param int[] $filesToDelete */ private function deleteExtraFiles(array $filesToDelete): void { @@ -82,7 +82,7 @@ private function deleteExtraFiles(array $filesToDelete): void } /** - * @param SplFileInfo[] $filesToUpdate + * @param int[] $filesToUpdate */ private function updateChangedFiles(array $filesToUpdate): void { From 2c920036c202a380cf03ca8cab77f06715fe0d1a Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 13:47:34 +0100 Subject: [PATCH 12/14] Cover more failure cases --- composer.json | 3 ++- src/Application.php | 7 ++++--- src/Bridge/Psr7/RequestFactory.php | 3 +++ src/Cli/InvokeCommand.php | 10 +++++++--- src/Console/Deployer.php | 17 ++++++++++++++--- src/Http/LambdaResponse.php | 3 ++- src/Http/WelcomeHandler.php | 3 +++ 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 1072ab89d..204bdc747 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "zendframework/zend-diactoros": "^1.6", "jolicode/jolinotif": "^2.0", "matomo/ini": "^2.0", - "riverline/multipart-parser": "^1.2" + "riverline/multipart-parser": "^1.2", + "innmind/json": "^1.0" }, "require-dev": { "phpunit/phpunit": "^6.5", diff --git a/src/Application.php b/src/Application.php index 365bab671..00cff101a 100644 --- a/src/Application.php +++ b/src/Application.php @@ -7,6 +7,7 @@ use Bref\Cli\WelcomeApplication; use Bref\Http\LambdaResponse; use Bref\Http\WelcomeHandler; +use Innmind\Json\Json; use Psr\Http\Server\RequestHandlerInterface; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; @@ -122,14 +123,14 @@ public function run(): void $cliInput = new StringInput($event['cli']); $cliOutput = new BufferedOutput; $exitCode = $this->cliHandler->run($cliInput, $cliOutput); - $output = json_encode([ + $output = Json::encode([ 'exitCode' => $exitCode, 'output' => $cliOutput->fetch(), ]); } else { // Simple invocation $output = ($this->simpleHandler)($event); - $output = json_encode($output); + $output = Json::encode($output); } $this->writeLambdaOutput($output); @@ -147,7 +148,7 @@ private function readLambdaEvent(): array { // The lambda event is passed as JSON by `handler.js` as a CLI argument $argv = $_SERVER['argv']; - return json_decode($argv[1], true) ?: []; + return Json::decode($argv[1]) ?: []; } private function writeLambdaOutput(string $json): void diff --git a/src/Bridge/Psr7/RequestFactory.php b/src/Bridge/Psr7/RequestFactory.php index 09e0ae115..04b315dee 100644 --- a/src/Bridge/Psr7/RequestFactory.php +++ b/src/Bridge/Psr7/RequestFactory.php @@ -67,6 +67,9 @@ public static function fromLambdaEvent(array $event): ServerRequestInterface foreach ($document->getParts() as $part) { if ($part->isFile()) { $tmpPath = tempnam(sys_get_temp_dir(), 'bref_upload_'); + if ($tmpPath === false) { + throw new \RuntimeException('Unable to create a temporary directory'); + } file_put_contents($tmpPath, $part->getBody()); $file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType()); diff --git a/src/Cli/InvokeCommand.php b/src/Cli/InvokeCommand.php index a64b899d0..3dae4329a 100644 --- a/src/Cli/InvokeCommand.php +++ b/src/Cli/InvokeCommand.php @@ -2,6 +2,8 @@ namespace Bref\Cli; +use Innmind\Json\Exception\RuntimeException; +use Innmind\Json\Json; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -40,9 +42,11 @@ protected function execute(InputInterface $input, OutputInterface $output): void $eventOption = $input->getOption('event'); if ($eventOption) { - $event = json_decode($eventOption, true); - if ($event === null) { - throw new \RuntimeException('The `--event` option provided contains invalid JSON: ' . $eventOption); + $eventOption = (string) $eventOption; + try { + $event = Json::decode($eventOption); + } catch (RuntimeException $e) { + throw new \RuntimeException("The `--event` option provided contains invalid JSON: $eventOption", 0, $e); } } diff --git a/src/Console/Deployer.php b/src/Console/Deployer.php index 2d800be39..b234b7179 100644 --- a/src/Console/Deployer.php +++ b/src/Console/Deployer.php @@ -126,7 +126,7 @@ private function generateArchive(ProgressBar $progress): void * error if there are unknown keys. Using the Symfony Config component * for that could make sense. */ - $projectConfig = Yaml::parse(file_get_contents('.bref.yml')); + $projectConfig = Yaml::parse($this->readContent('.bref.yml')); } $progress->advance(); @@ -226,7 +226,7 @@ private function createProgressBar(SymfonyStyle $io, int $max): ProgressBar */ private function copyServerlessYml(): void { - $serverlessYml = Yaml::parse(file_get_contents('serverless.yml')); + $serverlessYml = Yaml::parse($this->readContent('serverless.yml')); // Force deploying the files used by Bref without having the user know about them $serverlessYml['package']['include'][] = 'handler.js'; @@ -300,7 +300,8 @@ private function removeUnusedLibraries(array $extensions): void $dependenciesFile = '.bref/output/.bref/bin/dependencies.yml'; if ($this->fs->exists($dependenciesFile)) { - $dependencies = Yaml::parse(file_get_contents($dependenciesFile))['extensions'] ?? []; + $dependenciesConfig = Yaml::parse($this->readContent($dependenciesFile)); + $dependencies = $dependenciesConfig['extensions'] ?? []; $this->fs->remove($dependenciesFile); } @@ -314,4 +315,14 @@ private function removeUnusedLibraries(array $extensions): void } } } + + private function readContent(string $file): string + { + $content = file_get_contents($file); + if ($content === false) { + throw new \RuntimeException("Unable to read the `$file` file"); + } + + return $content; + } } diff --git a/src/Http/LambdaResponse.php b/src/Http/LambdaResponse.php index 85107567c..8a5087563 100644 --- a/src/Http/LambdaResponse.php +++ b/src/Http/LambdaResponse.php @@ -2,6 +2,7 @@ namespace Bref\Http; +use Innmind\Json\Json; use Psr\Http\Message\ResponseInterface; /** @@ -64,7 +65,7 @@ public function toJson(): string // This is the format required by the AWS_PROXY lambda integration // See https://stackoverflow.com/questions/43708017/aws-lambda-api-gateway-error-malformed-lambda-proxy-response - return json_encode([ + return Json::encode([ 'isBase64Encoded' => false, 'statusCode' => $this->statusCode, 'headers' => $headers, diff --git a/src/Http/WelcomeHandler.php b/src/Http/WelcomeHandler.php index abdb7d7be..08d2ede80 100644 --- a/src/Http/WelcomeHandler.php +++ b/src/Http/WelcomeHandler.php @@ -15,6 +15,9 @@ class WelcomeHandler implements RequestHandlerInterface public function handle(ServerRequestInterface $request): ResponseInterface { $html = file_get_contents(__DIR__ . '/welcome.html'); + if ($html === false) { + throw new \RuntimeException('Unable to read the `welcome.html` template'); + } return new HtmlResponse($html); } From d3ec9773dc9b676a8d3de9dcc5d964a4f5b4f6ca Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 13:50:14 +0100 Subject: [PATCH 13/14] Require PHP 7.2 because this is actually the only version supported in Bref PHP 7.1 was supported but the code actually ran on 7.2 in production. So it doesn't make sense to give the wrong impression that 7.1 is supported. --- .travis.yml | 4 ++-- composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4d96fbd0..633b208ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ notifications: on_success: never php: - - 7.1 - 7.2 + - 7.3 - nightly matrix: @@ -14,7 +14,7 @@ matrix: allow_failures: - php: nightly include: - - php: 7.1 + - php: 7.2 env: dependencies=lowest cache: diff --git a/composer.json b/composer.json index 204bdc747..16ecfc5dd 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "bref" ], "require": { - "php": "^7.1.0", + "php": "^7.2.0", "ext-json": "*", "mnapoli/silly": "^1.7", "symfony/filesystem": "^3.1|^4.0", From 3e1fb782cb3d6e33a9b5cc5d8365bca78d6563d4 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sun, 25 Nov 2018 14:00:39 +0100 Subject: [PATCH 14/14] Remove PHP 7.3 support for now, will add in a separate PR --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 633b208ab..59880043b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ notifications: php: - 7.2 - - 7.3 - nightly matrix: