diff --git a/.github/workflows/php-workshop.yml b/.github/workflows/php-workshop.yml index 2c17e0af..f55b1402 100644 --- a/.github/workflows/php-workshop.yml +++ b/.github/workflows/php-workshop.yml @@ -35,8 +35,8 @@ jobs: - name: Run phpcs run: composer cs -# - name: Run phpstan -# run: composer static + - name: Run phpstan + run: composer static - name: Coverage upload if: matrix.php == '7.4' diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 231b1b13..00000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,5 +0,0 @@ -before_commands: - - "composer install --prefer-source" - -checks: - php: true \ No newline at end of file diff --git a/composer.json b/composer.json index d4c963ef..3b578b6c 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,10 @@ "require-dev": { "composer/composer": "^1.2", "squizlabs/php_codesniffer": "^3.4", - "symfony/phpunit-bridge": "^5.1" + "symfony/phpunit-bridge": "^5.1", + "phpstan/phpstan": "^0.12.50", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan-symfony": "^0.12.8" }, "autoload" : { "psr-4" : { @@ -54,6 +57,7 @@ "cs" : [ "phpcs src --standard=PSR12 --encoding=UTF-8", "phpcs test --standard=PSR12 --encoding=UTF-8" - ] + ], + "static": "phpstan --ansi analyse --level max src" } } diff --git a/composer.lock b/composer.lock index b65444ed..e5d7568e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0ea09ae1f35bb06e5b04906b19e16b78", + "content-hash": "bf9d4cab83dfee8219e63d4e0d93c71c", "packages": [ { "name": "aydin-hassan/cli-md-renderer", @@ -1656,6 +1656,177 @@ ], "time": "2020-05-27T16:41:55+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "5c2da3846819f951385cb6a25d3277051481c48a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/5c2da3846819f951385cb6a25d3277051481c48a", + "reference": "5c2da3846819f951385cb6a25d3277051481c48a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": ">=0.11.6" + }, + "require-dev": { + "composer/composer": "^1.8", + "consistence/coding-standard": "^3.8", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.0.2", + "phing/phing": "^2.16", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11", + "slevomat/coding-standard": "^5.0.4" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "time": "2020-08-30T12:06:42+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.12.50", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "b8248f9c81265af75d6d969ca3252aaf3e998f3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8248f9c81265af75d6d969ca3252aaf3e998f3a", + "reference": "b8248f9c81265af75d6d969ca3252aaf3e998f3a", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpstan", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2020-10-16T12:22:23+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "0.12.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "9da032e0874ea6bb21aa04ddf009e36ff595478f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/9da032e0874ea6bb21aa04ddf009e36ff595478f", + "reference": "9da032e0874ea6bb21aa04ddf009e36ff595478f", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "phpstan/phpstan": "^0.12.26" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.10", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.0.2", + "phing/phing": "^2.16.2", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^0.12.8", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpunit/phpunit": "^7.5.20", + "slevomat/coding-standard": "^6.4", + "squizlabs/php_codesniffer": "^3.5.6", + "symfony/console": "^4.0", + "symfony/framework-bundle": "^4.0", + "symfony/http-foundation": "^4.0", + "symfony/messenger": "^4.2", + "symfony/serializer": "^4.0" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "0.12-dev" + }, + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "time": "2020-10-21T15:45:33+00:00" + }, { "name": "psr/log", "version": "1.1.3", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..ae54b621 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + message: '#PhpSchool\\PhpWorkshop\\ExerciseRunner\\CliRunner\:\:preserveOldArgFormat\(\) should return#' + path: src/ExerciseRunner/CliRunner.php + - + message: '#Call to an undefined method PhpParser\\Node\\Expr\|PhpParser\\Node\\Name\:\:__toString\(\)#' + path: src/Check/FunctionRequirementsCheck.php diff --git a/src/Application.php b/src/Application.php index 18bff628..352f4d4c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -22,17 +22,17 @@ final class Application private $workshopTitle; /** - * @var array + * @var array */ private $checks = []; /** - * @var ExerciseInterface[] + * @var array */ private $exercises = []; /** - * @var array + * @var array */ private $results = []; @@ -68,7 +68,7 @@ final class Application * @param string $workshopTitle The workshop title - this is used throughout the application * @param string $diConfigFile The absolute path to the DI configuration file */ - public function __construct($workshopTitle, $diConfigFile) + public function __construct(string $workshopTitle, string $diConfigFile) { Assertion::string($workshopTitle); Assertion::file($diConfigFile); @@ -81,9 +81,9 @@ public function __construct($workshopTitle, $diConfigFile) * Register a custom check with the application. Exercises will only be able to use the check * if it has been registered here. * - * @param string $check The FQCN of the check + * @param class-string $check The FQCN of the check */ - public function addCheck($check) + public function addCheck(string $check): void { $this->checks[] = $check; } @@ -92,18 +92,18 @@ public function addCheck($check) * Register an exercise with the application. Only exercises registered here will * be displayed in the exercise menu. * - * @param string $exercise The FQCN of the check + * @param class-string $exercise The FQCN of the check */ - public function addExercise($exercise) + public function addExercise(string $exercise): void { $this->exercises[] = $exercise; } /** - * @param string $resultClass - * @param string $resultRendererClass + * @param class-string $resultClass + * @param class-string $resultRendererClass */ - public function addResult($resultClass, $resultRendererClass) + public function addResult(string $resultClass, string $resultRendererClass): void { Assertion::classExists($resultClass); Assertion::classExists($resultRendererClass); @@ -120,7 +120,7 @@ public function addResult($resultClass, $resultRendererClass) * * @param string $logo The logo */ - public function setLogo($logo) + public function setLogo(string $logo): void { Assertion::string($logo); $this->logo = $logo; @@ -132,7 +132,7 @@ public function setLogo($logo) * * @param string $colour The colour */ - public function setFgColour($colour) + public function setFgColour(string $colour): void { Assertion::string($colour); $this->fgColour = $colour; @@ -144,7 +144,7 @@ public function setFgColour($colour) * * @param string $colour The colour */ - public function setBgColour($colour) + public function setBgColour(string $colour): void { Assertion::string($colour); $this->bgColour = $colour; @@ -156,7 +156,7 @@ public function setBgColour($colour) * * @return int The exit code */ - public function run() + public function run(): int { $container = $this->getContainer(); @@ -218,7 +218,7 @@ public function run() /** * @return \DI\Container */ - private function getContainer() + private function getContainer(): \DI\Container { $containerBuilder = new ContainerBuilder(); $containerBuilder->addDefinitions( diff --git a/src/Check/CheckRepository.php b/src/Check/CheckRepository.php index 7565061c..19f245d3 100644 --- a/src/Check/CheckRepository.php +++ b/src/Check/CheckRepository.php @@ -11,12 +11,12 @@ class CheckRepository { /** - * @var CheckInterface[] + * @var array */ private $checks = []; /** - * @param CheckInterface[] $checks An array of checks available to the workshop framework. + * @param array $checks An array of checks available to the workshop framework. */ public function __construct(array $checks = []) { @@ -27,6 +27,7 @@ public function __construct(array $checks = []) /** * Add a new check to the repository. + * @param CheckInterface $check */ public function registerCheck(CheckInterface $check): void { @@ -36,7 +37,7 @@ public function registerCheck(CheckInterface $check): void /** * Get all of the checks in the repository. * - * @return array + * @return array */ public function getAll(): array { @@ -61,6 +62,9 @@ public function getByClass(string $class): CheckInterface /** * Query whether a check instance exists in this repository via its class name. + * + * @param string $class + * @return bool */ public function has(string $class): bool { diff --git a/src/Check/CodeParseCheck.php b/src/Check/CodeParseCheck.php index 1330ecf1..02b95bfe 100644 --- a/src/Check/CodeParseCheck.php +++ b/src/Check/CodeParseCheck.php @@ -51,13 +51,12 @@ public function getName(): string */ public function check(ExerciseInterface $exercise, Input $input): ResultInterface { - - $code = file_get_contents($input->getArgument('program')); + $code = (string) file_get_contents($input->getRequiredArgument('program')); try { $this->parser->parse($code); } catch (Error $e) { - return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getArgument('program')); + return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getRequiredArgument('program')); } return Success::fromCheck($this); diff --git a/src/Check/ComposerCheck.php b/src/Check/ComposerCheck.php index 1bef0aab..a2ba915f 100644 --- a/src/Check/ComposerCheck.php +++ b/src/Check/ComposerCheck.php @@ -41,19 +41,19 @@ public function check(ExerciseInterface $exercise, Input $input): ResultInterfac throw new \InvalidArgumentException(); } - if (!file_exists(sprintf('%s/composer.json', dirname($input->getArgument('program'))))) { + if (!file_exists(sprintf('%s/composer.json', dirname($input->getRequiredArgument('program'))))) { return new Failure($this->getName(), 'No composer.json file found'); } - if (!file_exists(sprintf('%s/composer.lock', dirname($input->getArgument('program'))))) { + if (!file_exists(sprintf('%s/composer.lock', dirname($input->getRequiredArgument('program'))))) { return new Failure($this->getName(), 'No composer.lock file found'); } - if (!file_exists(sprintf('%s/vendor', dirname($input->getArgument('program'))))) { + if (!file_exists(sprintf('%s/vendor', dirname($input->getRequiredArgument('program'))))) { return new Failure($this->getName(), 'No vendor folder found'); } - $lockFile = new LockFileParser(sprintf('%s/composer.lock', dirname($input->getArgument('program')))); + $lockFile = new LockFileParser(sprintf('%s/composer.lock', dirname($input->getRequiredArgument('program')))); $missingPackages = array_filter($exercise->getRequiredPackages(), function ($package) use ($lockFile) { return !$lockFile->hasInstalledPackage($package); }); diff --git a/src/Check/DatabaseCheck.php b/src/Check/DatabaseCheck.php index 2f1f65d1..d0c79e59 100644 --- a/src/Check/DatabaseCheck.php +++ b/src/Check/DatabaseCheck.php @@ -37,7 +37,7 @@ class DatabaseCheck implements ListenableCheckInterface private $solutionDatabasePath; /** - * @var + * @var string */ private $userDsn; diff --git a/src/Check/FileExistsCheck.php b/src/Check/FileExistsCheck.php index d569eaad..8327ff94 100644 --- a/src/Check/FileExistsCheck.php +++ b/src/Check/FileExistsCheck.php @@ -31,11 +31,14 @@ public function getName(): string */ public function check(ExerciseInterface $exercise, Input $input): ResultInterface { - if (file_exists($input->getArgument('program'))) { + if (file_exists($input->getRequiredArgument('program'))) { return Success::fromCheck($this); } - return Failure::fromCheckAndReason($this, sprintf('File: "%s" does not exist', $input->getArgument('program'))); + return Failure::fromCheckAndReason( + $this, + sprintf('File: "%s" does not exist', $input->getRequiredArgument('program')) + ); } /** diff --git a/src/Check/FunctionRequirementsCheck.php b/src/Check/FunctionRequirementsCheck.php index ba42f3ea..e275daff 100644 --- a/src/Check/FunctionRequirementsCheck.php +++ b/src/Check/FunctionRequirementsCheck.php @@ -4,6 +4,7 @@ use PhpParser\Error; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name; use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; @@ -15,6 +16,7 @@ use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure; use PhpSchool\PhpWorkshop\Result\ResultInterface; use PhpSchool\PhpWorkshop\Result\Success; +use PhpSchool\PhpWorkshop\Utils\Collection; /** * This check verifies that the student's solution contains usages of some required functions @@ -22,7 +24,7 @@ */ class FunctionRequirementsCheck implements SimpleCheckInterface { - + /** * @var Parser */ @@ -62,12 +64,12 @@ public function check(ExerciseInterface $exercise, Input $input): ResultInterfac $requiredFunctions = $exercise->getRequiredFunctions(); $bannedFunctions = $exercise->getBannedFunctions(); - $code = file_get_contents($input->getArgument('program')); + $code = (string) file_get_contents($input->getRequiredArgument('program')); try { - $ast = $this->parser->parse($code); + $ast = $this->parser->parse($code) ?? []; } catch (Error $e) { - return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getArgument('program')); + return Failure::fromCheckAndCodeParseFailure($this, $e, $input->getRequiredArgument('program')); } $visitor = new FunctionVisitor($requiredFunctions, $bannedFunctions); diff --git a/src/CodeInsertion.php b/src/CodeInsertion.php index 0b4c3f48..b1866a7e 100644 --- a/src/CodeInsertion.php +++ b/src/CodeInsertion.php @@ -23,7 +23,7 @@ class CodeInsertion public const TYPE_AFTER = 'after'; /** - * @var array + * @var array */ private $types = [ self::TYPE_BEFORE, @@ -47,11 +47,10 @@ class CodeInsertion * @param string $type * @param string $code */ - public function __construct($type, $code) + public function __construct(string $type, string $code) { Assertion::inArray($type, $this->types); - Assertion::string($code); - + $this->type = $type; $this->code = $code; } @@ -61,7 +60,7 @@ public function __construct($type, $code) * * @return string */ - public function getType() + public function getType(): string { return $this->type; } @@ -71,7 +70,7 @@ public function getType() * * @return string */ - public function getCode() + public function getCode(): string { return $this->code; } diff --git a/src/CodePatcher.php b/src/CodePatcher.php index aabac59f..6a38671f 100644 --- a/src/CodePatcher.php +++ b/src/CodePatcher.php @@ -3,6 +3,7 @@ namespace PhpSchool\PhpWorkshop; use PhpParser\Error; +use PhpParser\Node\Stmt; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; @@ -26,7 +27,7 @@ class CodePatcher private $printer; /** - * @var Patch + * @var Patch|null */ private $defaultPatch; @@ -40,12 +41,12 @@ class CodePatcher * * @param Parser $parser * @param Standard $printer - * @param Patch $defaultPatch + * @param Patch|null $defaultPatch */ public function __construct(Parser $parser, Standard $printer, Patch $defaultPatch = null) { - $this->parser = $parser; - $this->printer = $printer; + $this->parser = $parser; + $this->printer = $printer; $this->defaultPatch = $defaultPatch; } @@ -61,7 +62,7 @@ public function __construct(Parser $parser, Standard $printer, Patch $defaultPat * @param string $code * @return string */ - public function patch(ExerciseInterface $exercise, $code) + public function patch(ExerciseInterface $exercise, string $code): string { if (null !== $this->defaultPatch) { $code = $this->applyPatch($this->defaultPatch, $code); @@ -79,7 +80,7 @@ public function patch(ExerciseInterface $exercise, $code) * @param string $code * @return string */ - private function applyPatch(Patch $patch, $code) + private function applyPatch(Patch $patch, string $code): string { $statements = $this->parser->parse($code); foreach ($patch->getModifiers() as $modifier) { @@ -99,15 +100,15 @@ private function applyPatch(Patch $patch, $code) /** * @param CodeInsertion $codeInsertion - * @param array $statements - * @return array + * @param array $statements + * @return array */ - private function applyCodeInsertion(CodeInsertion $codeInsertion, array $statements) + private function applyCodeInsertion(CodeInsertion $codeInsertion, array $statements): array { try { $codeToInsert = $codeInsertion->getCode(); $codeToInsert = sprintf('parser->parse($codeToInsert); + $additionalStatements = $this->parser->parse($codeToInsert) ?? []; } catch (Error $e) { //we should probably log this and have a dev mode or something return $statements; diff --git a/src/Command/CreditsCommand.php b/src/Command/CreditsCommand.php index c5951225..182a1805 100644 --- a/src/Command/CreditsCommand.php +++ b/src/Command/CreditsCommand.php @@ -21,52 +21,52 @@ class CreditsCommand private $color; /** - * @var array + * @var array */ private $coreContributors; /** - * @var array + * @var array */ private $appContributors; /** - * @param array $coreContributors - * @param array $appContributors + * @param array $coreContributors + * @param array $appContributors * @param OutputInterface $output * @param Color $color */ public function __construct(array $coreContributors, array $appContributors, OutputInterface $output, Color $color) { $this->coreContributors = $coreContributors; - $this->appContributors = $appContributors; - $this->output = $output; - $this->color = $color; + $this->appContributors = $appContributors; + $this->output = $output; + $this->color = $color; } /** * Output contributors in columns * - * @param array $contributors + * @param array $contributors */ - private function writeContributors(array $contributors) + private function writeContributors(array $contributors): void { $nameColumnSize = max(array_map('strlen', array_values($contributors))); - $columns = sprintf('%s GitHub Username', str_pad('Name', $nameColumnSize)); + $columns = sprintf('%s GitHub Username', str_pad('Name', (int) $nameColumnSize)); $this->output->writeLine($columns); $this->output->writeLine(str_repeat('-', strlen($columns))); foreach ($contributors as $gitHubUser => $name) { - $this->output->writeLine(sprintf("%s %s", str_pad($name, $nameColumnSize), $gitHubUser)); + $this->output->writeLine(sprintf("%s %s", str_pad($name, (int) $nameColumnSize), $gitHubUser)); } } /** * - * @return int|void + * @return void */ - public function __invoke() + public function __invoke(): void { if (empty($this->coreContributors)) { return; diff --git a/src/Command/MenuCommandInvoker.php b/src/Command/MenuCommandInvoker.php index 9341c751..2faaea33 100644 --- a/src/Command/MenuCommandInvoker.php +++ b/src/Command/MenuCommandInvoker.php @@ -25,7 +25,7 @@ public function __construct(callable $command) /** * @param CliMenu $menu */ - public function __invoke(CliMenu $menu) + public function __invoke(CliMenu $menu): void { $menu->close(); $command = $this->command; diff --git a/src/Command/PrintCommand.php b/src/Command/PrintCommand.php index d8b88857..c6b62447 100644 --- a/src/Command/PrintCommand.php +++ b/src/Command/PrintCommand.php @@ -2,6 +2,7 @@ namespace PhpSchool\PhpWorkshop\Command; +use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\ExerciseRepository; use PhpSchool\PhpWorkshop\MarkdownRenderer; use PhpSchool\PhpWorkshop\Output\OutputInterface; @@ -66,7 +67,7 @@ public function __invoke() $currentExercise = $this->userState->getCurrentExercise(); $exercise = $this->exerciseRepository->findByName($currentExercise); - $markDown = file_get_contents($exercise->getProblem()); + $markDown = (string) file_get_contents($exercise->getProblem()); $doc = $this->markdownRenderer->render($markDown); $doc = str_replace('{appname}', $this->appName, $doc); $this->output->write($doc); diff --git a/src/CommandArgument.php b/src/CommandArgument.php index e282df8c..2c69f85d 100644 --- a/src/CommandArgument.php +++ b/src/CommandArgument.php @@ -21,7 +21,7 @@ class CommandArgument * @param string $name The name of the argument * @param bool $optional Whether it is required or not */ - public function __construct($name, $optional = false) + public function __construct(string $name, bool $optional = false) { $this->name = $name; $this->optional = $optional; @@ -29,20 +29,20 @@ public function __construct($name, $optional = false) /** * @param string $name - * @return static + * @return self */ public static function optional($name) { - return new static($name, true); + return new self($name, true); } /** * @param string $name - * @return static + * @return self */ public static function required($name) { - return new static($name); + return new self($name); } /** diff --git a/src/CommandRouter.php b/src/CommandRouter.php index e5cc1e7e..15d65357 100644 --- a/src/CommandRouter.php +++ b/src/CommandRouter.php @@ -15,9 +15,8 @@ */ class CommandRouter { - /** - * @var CommandDefinition[] + * @var array */ private $commands; @@ -43,14 +42,14 @@ class CommandRouter * Also accepts an instance of the container so it can look for services in there which may by defined * as the callable for one of the command definitions. * - * @param CommandDefinition[] $commands An array of command definitions + * @param array $commands An array of command definitions * @param string $default The default command to use (if the workshop was invoked with no arguments) * @param EventDispatcher $eventDispatcher * @param ContainerInterface $container An instance of the container */ public function __construct( array $commands, - $default, + string $default, EventDispatcher $eventDispatcher, ContainerInterface $container ) { @@ -61,15 +60,15 @@ public function __construct( if (!isset($this->commands[$default])) { throw new \InvalidArgumentException(sprintf('Default command: "%s" is not available', $default)); } - $this->defaultCommand = $default; - $this->eventDispatcher = $eventDispatcher; - $this->container = $container; + $this->defaultCommand = $default; + $this->eventDispatcher = $eventDispatcher; + $this->container = $container; } /** * @param CommandDefinition $c */ - private function addCommand(CommandDefinition $c) + private function addCommand(CommandDefinition $c): void { if (isset($this->commands[$c->getName()])) { throw new \InvalidArgumentException(sprintf('Command with name: "%s" already exists', $c->getName())); @@ -91,11 +90,11 @@ private function addCommand(CommandDefinition $c) * Finally, the callable is invoked with the arguments passed from the cli. The return value of * callable is returned (if it is an integer, if not zero (success) is returned). * - * @param array $args + * @param array|null $args * @return int * @throws CliRouteNotExistsException */ - public function route(array $args = null) + public function route(array $args = null): int { if (null === $args) { @@ -112,7 +111,7 @@ public function route(array $args = null) if (!isset($this->commands[$commandName])) { $command = $this->findNearestCommand($commandName, $this->commands); - if (false === $command) { + if (null === $command) { throw new CliRouteNotExistsException($commandName); } @@ -131,17 +130,17 @@ public function route(array $args = null) * @param string $commandName * @param CommandArgument[] $definitionArgs * @param string $appName - * @param array $givenArgs + * @param array $givenArgs * @return Input */ - private function parseArgs($commandName, array $definitionArgs, $appName, array $givenArgs) + private function parseArgs(string $commandName, array $definitionArgs, string $appName, array $givenArgs): Input { $parsedArgs = []; while (null !== ($definitionArg = array_shift($definitionArgs))) { $arg = array_shift($givenArgs); - if (null == $arg && !$definitionArg->isOptional()) { + if (null === $arg && !$definitionArg->isOptional()) { throw new MissingArgumentException($commandName, array_map(function (CommandArgument $argument) { return $argument->getName(); }, array_merge([$definitionArg], $definitionArgs))); @@ -158,10 +157,10 @@ private function parseArgs($commandName, array $definitionArgs, $appName, array * characters different * * @param string $commandName - * @param array $commands - * @return string|false + * @param array $commands + * @return string|null */ - private function findNearestCommand($commandName, array $commands) + private function findNearestCommand(string $commandName, array $commands): ?string { $distances = []; foreach (array_keys($commands) as $command) { @@ -173,10 +172,11 @@ private function findNearestCommand($commandName, array $commands) }); if (empty($distances)) { - return false; + return null; } - return array_search(min($distances), $distances); + $name = array_search(min($distances), $distances, true); + return is_string($name) ? $name : null; } /** @@ -184,12 +184,13 @@ private function findNearestCommand($commandName, array $commands) * @param Input $input * @return int */ - private function resolveCallable(CommandDefinition $command, Input $input) + private function resolveCallable(CommandDefinition $command, Input $input): int { $commandCallable = $command->getCommandCallable(); if (is_callable($commandCallable)) { - return $this->callCommand($command, $commandCallable, $input); + $return = $this->callCommand($command, $commandCallable, $input); + return is_int($return) ? $return : 0; } if (!is_string($commandCallable)) { @@ -208,18 +209,14 @@ private function resolveCallable(CommandDefinition $command, Input $input) $return = $this->callCommand($command, $callable, $input); - if (is_int($return)) { - return $return; - } - - return 0; + return is_int($return) ? $return : 0; } /** * @param CommandDefinition $command * @param callable $callable * @param Input $input - * @return int + * @return ?int */ private function callCommand(CommandDefinition $command, callable $callable, Input $input) { diff --git a/src/ComposerUtil/LockFileParser.php b/src/ComposerUtil/LockFileParser.php index 6535b60f..6620c0ce 100644 --- a/src/ComposerUtil/LockFileParser.php +++ b/src/ComposerUtil/LockFileParser.php @@ -10,7 +10,7 @@ class LockFileParser { /** - * @var string + * @var array */ private $contents; @@ -18,13 +18,17 @@ class LockFileParser * @param string $lockFilePath The absolute path to the `composer.lock` file. * @throws InvalidArgumentException If the file does not exist. */ - public function __construct($lockFilePath) + public function __construct(string $lockFilePath) { if (!file_exists($lockFilePath)) { throw new InvalidArgumentException(sprintf('Lock File: "%s" does not exist', $lockFilePath)); } - $this->contents = json_decode(file_get_contents($lockFilePath), true); + $this->contents = json_decode((string) file_get_contents($lockFilePath), true); + + if (!isset($this->contents['packages'])) { + throw new InvalidArgumentException(sprintf('Lock File: "%s" does not contain packages key', $lockFilePath)); + } } /** @@ -37,9 +41,9 @@ public function __construct($lockFilePath) * ]; * ``` * - * @return array + * @return array */ - public function getInstalledPackages() + public function getInstalledPackages(): array { return array_map(function (array $packageDetails) { return [ @@ -55,7 +59,7 @@ public function getInstalledPackages() * @param string $packageName * @return bool */ - public function hasInstalledPackage($packageName) + public function hasInstalledPackage($packageName): bool { foreach ($this->contents['packages'] as $packageDetails) { if ($packageName === $packageDetails['name']) { diff --git a/src/Event/CgiExecuteEvent.php b/src/Event/CgiExecuteEvent.php index d5559383..e174aa14 100644 --- a/src/Event/CgiExecuteEvent.php +++ b/src/Event/CgiExecuteEvent.php @@ -10,7 +10,6 @@ */ class CgiExecuteEvent extends Event { - /** * @var RequestInterface */ @@ -19,9 +18,9 @@ class CgiExecuteEvent extends Event /** * @param string $name The event name. * @param RequestInterface $request The request that will be performed. - * @param array $parameters The event parameters. + * @param array $parameters The event parameters. */ - public function __construct($name, RequestInterface $request, array $parameters = []) + public function __construct(string $name, RequestInterface $request, array $parameters = []) { $parameters['request'] = $request; parent::__construct($name, $parameters); @@ -32,9 +31,9 @@ public function __construct($name, RequestInterface $request, array $parameters * Add a header to the request. * * @param string $header - * @param string|string[] $value + * @param string|array $value */ - public function addHeaderToRequest($header, $value) + public function addHeaderToRequest(string $header, $value): void { $this->request = $this->request->withHeader($header, $value); } @@ -44,7 +43,7 @@ public function addHeaderToRequest($header, $value) * * @param callable $callback */ - public function modifyRequest(callable $callback) + public function modifyRequest(callable $callback): void { $this->request = $callback($this->request); } @@ -54,7 +53,7 @@ public function modifyRequest(callable $callback) * * @return RequestInterface */ - public function getRequest() + public function getRequest(): RequestInterface { return $this->request; } diff --git a/src/Event/CliExecuteEvent.php b/src/Event/CliExecuteEvent.php index 8db99dd9..43d520b7 100644 --- a/src/Event/CliExecuteEvent.php +++ b/src/Event/CliExecuteEvent.php @@ -12,16 +12,16 @@ class CliExecuteEvent extends Event { /** - * @var ArrayObject + * @var ArrayObject */ private $args; /** * @param string $name The event name. - * @param ArrayObject $args The arguments that should be/have been passed to the program. - * @param array $parameters The event parameters. + * @param ArrayObject $args The arguments that should be/have been passed to the program. + * @param array $parameters The event parameters. */ - public function __construct($name, ArrayObject $args, array $parameters = []) + public function __construct(string $name, ArrayObject $args, array $parameters = []) { $parameters['args'] = $args; parent::__construct($name, $parameters); @@ -33,9 +33,8 @@ public function __construct($name, ArrayObject $args, array $parameters = []) * * @param string $arg */ - public function prependArg($arg) + public function prependArg(string $arg): void { - Assertion::string($arg); $this->args = $this->args->prepend($arg); } @@ -44,18 +43,17 @@ public function prependArg($arg) * * @param string $arg */ - public function appendArg($arg) + public function appendArg(string $arg): void { - Assertion::string($arg); $this->args = $this->args->append($arg); } /** * Get the arguments to be passed to the program. * - * @return ArrayObject + * @return ArrayObject */ - public function getArgs() + public function getArgs(): ArrayObject { return $this->args; } diff --git a/src/Event/ContainerListenerHelper.php b/src/Event/ContainerListenerHelper.php index c96149ba..7ee2614d 100644 --- a/src/Event/ContainerListenerHelper.php +++ b/src/Event/ContainerListenerHelper.php @@ -18,7 +18,7 @@ class ContainerListenerHelper private $method; /** - * @param $service + * @param string $service * @param string $method */ public function __construct($service, $method = '__invoke') diff --git a/src/Event/Event.php b/src/Event/Event.php index 4b47fd90..9abe8d26 100644 --- a/src/Event/Event.php +++ b/src/Event/Event.php @@ -15,15 +15,15 @@ class Event implements EventInterface private $name; /** - * @var array + * @var array */ protected $parameters; /** * @param string $name The event name. - * @param array $parameters The event parameters. + * @param array $parameters The event parameters. */ - public function __construct($name, array $parameters = []) + public function __construct(string $name, array $parameters = []) { $this->name = $name; $this->parameters = $parameters; @@ -34,7 +34,7 @@ public function __construct($name, array $parameters = []) * * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -42,9 +42,9 @@ public function getName() /** * Get an array of parameters that were triggered with this event. * - * @return mixed[] + * @return array */ - public function getParameters() + public function getParameters(): array { return $this->parameters; } @@ -56,7 +56,7 @@ public function getParameters() * @return mixed The value. * @throws InvalidArgumentException If the parameter by name does not exist. */ - public function getParameter($name) + public function getParameter(string $name) { if (!array_key_exists($name, $this->parameters)) { throw new InvalidArgumentException(sprintf('Parameter: "%s" does not exist', $name)); diff --git a/src/Event/EventDispatcher.php b/src/Event/EventDispatcher.php index 88fd80f5..b8c7ee5e 100644 --- a/src/Event/EventDispatcher.php +++ b/src/Event/EventDispatcher.php @@ -11,7 +11,7 @@ class EventDispatcher { /** - * @var array + * @var array> */ private $listeners = []; @@ -49,7 +49,7 @@ public function dispatch(EventInterface $event): EventInterface * Attach a callback to an event name. `$eventNames` can be an array of event names in order to attach the same * callback to multiple events or it can just be one event name as a string. * - * @param string|array $eventNames + * @param string|array $eventNames * @param callable $callback */ public function listen($eventNames, callable $callback): void @@ -64,10 +64,10 @@ public function listen($eventNames, callable $callback): void } /** - * @param string|array $eventName + * @param string $eventName * @param callable $callback */ - private function attachListener($eventName, callable $callback): void + private function attachListener(string $eventName, callable $callback): void { if (!array_key_exists($eventName, $this->listeners)) { $this->listeners[$eventName] = [$callback]; @@ -81,10 +81,10 @@ private function attachListener($eventName, callable $callback): void * A verifier should return an object which implements `PhpSchool\PhpWorkshop\Result\FailureInterface` * or `PhpSchool\PhpWorkshop\Result\SuccessInterface`. This result object will be added to the result aggregator. * - * @param string|array $eventName + * @param string $eventName * @param callable $verifier */ - public function insertVerifier($eventName, callable $verifier): void + public function insertVerifier(string $eventName, callable $verifier): void { $this->attachListener($eventName, function (EventInterface $event) use ($verifier) { $result = $verifier($event); @@ -99,7 +99,7 @@ public function insertVerifier($eventName, callable $verifier): void } /** - * @return array + * @return array> */ public function getListeners(): array { diff --git a/src/Event/EventInterface.php b/src/Event/EventInterface.php index 2cb20651..2698989f 100644 --- a/src/Event/EventInterface.php +++ b/src/Event/EventInterface.php @@ -2,6 +2,8 @@ namespace PhpSchool\PhpWorkshop\Event; +use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException; + /** * An event representation. */ @@ -12,14 +14,14 @@ interface EventInterface * * @return string */ - public function getName(); + public function getName(): string; /** * Get an array of parameters that were triggered with this event. * - * @return mixed[] + * @return array */ - public function getParameters(); + public function getParameters(): array; /** * Get a parameter by it's name. @@ -28,5 +30,5 @@ public function getParameters(); * @return mixed The value. * @throws InvalidArgumentException If the parameter by name does not exist. */ - public function getParameter($name); + public function getParameter(string $name); } diff --git a/src/Event/ExerciseRunnerEvent.php b/src/Event/ExerciseRunnerEvent.php index 153b7411..06eedcd8 100644 --- a/src/Event/ExerciseRunnerEvent.php +++ b/src/Event/ExerciseRunnerEvent.php @@ -10,7 +10,6 @@ */ class ExerciseRunnerEvent extends Event { - /** * @var ExerciseInterface */ @@ -25,9 +24,9 @@ class ExerciseRunnerEvent extends Event * @param string $name * @param ExerciseInterface $exercise * @param Input $input - * @param array $parameters + * @param array $parameters */ - public function __construct($name, ExerciseInterface $exercise, Input $input, array $parameters = []) + public function __construct(string $name, ExerciseInterface $exercise, Input $input, array $parameters = []) { $parameters['input'] = $input; $parameters['exercise'] = $exercise; @@ -40,7 +39,7 @@ public function __construct($name, ExerciseInterface $exercise, Input $input, ar /** * @return Input */ - public function getInput() + public function getInput(): Input { return $this->input; } @@ -48,7 +47,7 @@ public function getInput() /** * @return ExerciseInterface */ - public function getExercise() + public function getExercise(): ExerciseInterface { return $this->exercise; } diff --git a/src/Exception/CheckNotApplicableException.php b/src/Exception/CheckNotApplicableException.php index 5f90e0e0..7606cf9d 100644 --- a/src/Exception/CheckNotApplicableException.php +++ b/src/Exception/CheckNotApplicableException.php @@ -17,11 +17,11 @@ class CheckNotApplicableException extends RuntimeException * * @param CheckInterface $check The check Instance. * @param ExerciseInterface $exercise The exercise Instance. - * @return static + * @return self */ public static function fromCheckAndExercise(CheckInterface $check, ExerciseInterface $exercise) { - return new static( + return new self( sprintf( 'Check: "%s" cannot process exercise: "%s" with type: "%s"', $check->getName(), diff --git a/src/Exception/CodeExecutionException.php b/src/Exception/CodeExecutionException.php index 4a23ac5a..758d8000 100644 --- a/src/Exception/CodeExecutionException.php +++ b/src/Exception/CodeExecutionException.php @@ -14,11 +14,11 @@ class CodeExecutionException extends RuntimeException * Static constructor to create an instance from a failed `Symfony\Component\Process\Process` instance. * * @param Process $process The `Symfony\Component\Process\Process` instance which failed. - * @return static + * @return self */ public static function fromProcess(Process $process) { - return new static( + return new self( sprintf( 'PHP Code failed to execute. Error: "%s"', trim($process->getErrorOutput() ?: $process->getOutput()) diff --git a/src/Exception/ExerciseNotAssignedException.php b/src/Exception/ExerciseNotAssignedException.php new file mode 100644 index 00000000..490276f9 --- /dev/null +++ b/src/Exception/ExerciseNotAssignedException.php @@ -0,0 +1,19 @@ +getName(), $interface)); + return new self(sprintf('Exercise: "%s" should implement interface: "%s"', $exercise->getName(), $interface)); } } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 8fa6cf31..3db70f61 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -12,11 +12,11 @@ class InvalidArgumentException extends \InvalidArgumentException * * @param string $expected * @param mixed $actual - * @return static + * @return self */ - public static function typeMisMatch($expected, $actual) + public static function typeMisMatch(string $expected, $actual): self { - return new static( + return new self( sprintf( 'Expected: "%s" Received: "%s"', $expected, @@ -31,11 +31,11 @@ public static function typeMisMatch($expected, $actual) * @param string $parameterName * @param mixed[] $allowedValues * @param mixed $actualValue - * @return static + * @return self */ - public static function notValidParameter($parameterName, array $allowedValues, $actualValue) + public static function notValidParameter(string $parameterName, array $allowedValues, $actualValue): self { - return new static( + return new self( sprintf( 'Parameter: "%s" can only be one of: "%s" Received: "%s"', $parameterName, @@ -47,12 +47,12 @@ public static function notValidParameter($parameterName, array $allowedValues, $ /** * @param object $object - * @param string $requiredInterface - * @return static + * @param class-string $requiredInterface + * @return self */ - public static function missingImplements($object, $requiredInterface) + public static function missingImplements($object, string $requiredInterface): self { - return new static( + return new self( sprintf( '"%s" is required to implement "%s", but it does not', get_class($object), @@ -62,17 +62,17 @@ public static function missingImplements($object, $requiredInterface) } /** - * @param $value + * @param mixed $value * @return string */ - private static function stringify($value) + private static function stringify($value): string { if (is_object($value)) { return get_class($value); } if (is_array($value)) { - return implode('", "', array_map([static::class, 'stringify'], $value)); + return implode('", "', array_map([self::class, 'stringify'], $value)); } if (is_bool($value)) { @@ -83,7 +83,6 @@ private static function stringify($value) return (string) $value; } - return 'unknown'; } } diff --git a/src/Exception/MissingArgumentException.php b/src/Exception/MissingArgumentException.php index aeebbc6e..3b4937c5 100644 --- a/src/Exception/MissingArgumentException.php +++ b/src/Exception/MissingArgumentException.php @@ -10,7 +10,7 @@ class MissingArgumentException extends RuntimeException { /** - * @var array + * @var array */ private $missingArguments = []; @@ -18,9 +18,9 @@ class MissingArgumentException extends RuntimeException * Create the exception, requires the command name and missing arguments. * * @param string $commandName The command name. - * @param array $missingArguments An array of missing arguments (strings). + * @param array $missingArguments An array of missing arguments. */ - public function __construct($commandName, array $missingArguments) + public function __construct(string $commandName, array $missingArguments) { $this->missingArguments = $missingArguments; parent::__construct( @@ -35,9 +35,9 @@ public function __construct($commandName, array $missingArguments) /** * Retrieve the list of missing arguments. * - * @return array + * @return array */ - public function getMissingArguments() + public function getMissingArguments(): array { return $this->missingArguments; } diff --git a/src/Exercise/AbstractExercise.php b/src/Exercise/AbstractExercise.php index f2ad9360..5488aa87 100644 --- a/src/Exercise/AbstractExercise.php +++ b/src/Exercise/AbstractExercise.php @@ -19,7 +19,7 @@ abstract class AbstractExercise * * @return string */ - abstract public function getName(); + abstract public function getName(): string; /** * This returns a single file solution named `solution.php` which @@ -31,13 +31,13 @@ abstract public function getName(); * * @return SolutionInterface */ - public function getSolution() + public function getSolution(): SolutionInterface { return SingleFileSolution::fromFile( - realpath( + (string) realpath( sprintf( '%s/../../exercises/%s/solution/solution.php', - dirname((new ReflectionClass(static::class))->getFileName()), + dirname((string) (new ReflectionClass(static::class))->getFileName()), self::normaliseName($this->getName()) ) ) @@ -50,10 +50,10 @@ public function getSolution() * * @return string */ - public function getProblem() + public function getProblem(): string { $name = self::normaliseName($this->getName()); - $dir = dirname((new ReflectionClass(static::class))->getFileName()); + $dir = dirname((string) (new ReflectionClass(static::class))->getFileName()); return sprintf('%s/../../exercises/%s/problem/problem.md', $dir, $name); } @@ -63,7 +63,7 @@ public function getProblem() * * @return void */ - public function tearDown() + public function tearDown(): void { } @@ -71,9 +71,9 @@ public function tearDown() * @param string $name * @return string */ - public static function normaliseName($name) + public static function normaliseName(string $name): string { - return preg_replace('/[^A-Za-z\-]+/', '', str_replace(' ', '-', strtolower($name))); + return (string) preg_replace('/[^A-Za-z\-]+/', '', str_replace(' ', '-', strtolower($name))); } /** @@ -82,7 +82,7 @@ public static function normaliseName($name) * * @param ExerciseDispatcher $dispatcher */ - public function configure(ExerciseDispatcher $dispatcher) + public function configure(ExerciseDispatcher $dispatcher): void { } } diff --git a/src/Exercise/ExerciseInterface.php b/src/Exercise/ExerciseInterface.php index 9f049e00..ff16ba64 100644 --- a/src/Exercise/ExerciseInterface.php +++ b/src/Exercise/ExerciseInterface.php @@ -17,21 +17,21 @@ interface ExerciseInterface * * @return string */ - public function getName(); + public function getName(): string; /** * Return the type of exercise. This is an ENUM. See `PhpSchool\PhpWorkshop\Exercise\ExerciseType`. * * @return ExerciseType */ - public function getType(); + public function getType(): ExerciseType; /** * Get the absolute path to the markdown file which contains the exercise problem. * * @return string */ - public function getProblem(); + public function getProblem(): string; /** * This is where the exercise specifies the extra checks it may require. It is also @@ -41,14 +41,14 @@ public function getProblem(); * * @param ExerciseDispatcher $dispatcher */ - public function configure(ExerciseDispatcher $dispatcher); + public function configure(ExerciseDispatcher $dispatcher): void; /** * A short description of the exercise. * * @return string */ - public function getDescription(); + public function getDescription(): string; /** * Allows to perform some cleanup after the exercise solution's have been executed, for example @@ -56,5 +56,5 @@ public function getDescription(); * * @return void */ - public function tearDown(); + public function tearDown(): void; } diff --git a/src/Exercise/ExerciseType.php b/src/Exercise/ExerciseType.php index 88640292..4259947b 100644 --- a/src/Exercise/ExerciseType.php +++ b/src/Exercise/ExerciseType.php @@ -12,6 +12,7 @@ * $typeCgi = ExerciseType::CGI(); * $typeCustom = ExerciseType::CUSTOM(); * ``` + * @extends Enum */ class ExerciseType extends Enum { @@ -19,16 +20,15 @@ class ExerciseType extends Enum public const CGI = 'CGI'; public const CUSTOM = 'CUSTOM'; - /** * Map of exercise types to the required interfaces exercises of that particular * type should implement. * - * @var array + * @var array */ private static $exerciseTypeToExerciseInterfaceMap = [ - self::CLI => CliExercise::class, - self::CGI => CgiExercise::class, + self::CLI => CliExercise::class, + self::CGI => CgiExercise::class, self::CUSTOM => CustomVerifyingExercise::class, ]; @@ -36,9 +36,9 @@ class ExerciseType extends Enum * Get the FQCN of the interface this exercise should implement for this * exercise type. * - * @return string + * @return class-string */ - public function getExerciseInterface() + public function getExerciseInterface(): string { return static::$exerciseTypeToExerciseInterfaceMap[$this->getKey()]; } diff --git a/src/Exercise/TemporaryDirectoryTrait.php b/src/Exercise/TemporaryDirectoryTrait.php index 61ee5790..d8e11e8c 100644 --- a/src/Exercise/TemporaryDirectoryTrait.php +++ b/src/Exercise/TemporaryDirectoryTrait.php @@ -18,7 +18,7 @@ public function getTemporaryPath(): string { return sprintf( '%s/%s', - str_replace('\\', '/', realpath(sys_get_temp_dir())), + str_replace('\\', '/', (string) realpath(sys_get_temp_dir())), str_replace('\\', '_', __CLASS__) ); } diff --git a/src/ExerciseCheck/ComposerExerciseCheck.php b/src/ExerciseCheck/ComposerExerciseCheck.php index cba2efaf..568863fe 100644 --- a/src/ExerciseCheck/ComposerExerciseCheck.php +++ b/src/ExerciseCheck/ComposerExerciseCheck.php @@ -12,7 +12,7 @@ interface ComposerExerciseCheck * Returns an array of composer package names that student's solution should * have required via composer. * - * @return array An array of composer package names. + * @return array An array of composer package names. */ - public function getRequiredPackages(); + public function getRequiredPackages(): array; } diff --git a/src/ExerciseDispatcher.php b/src/ExerciseDispatcher.php index 3606ba12..77292b37 100644 --- a/src/ExerciseDispatcher.php +++ b/src/ExerciseDispatcher.php @@ -65,10 +65,10 @@ public function __construct( EventDispatcher $eventDispatcher, CheckRepository $checkRepository ) { - $this->runnerManager = $runnerManager; - $this->results = $resultAggregator; - $this->eventDispatcher = $eventDispatcher; - $this->checkRepository = $checkRepository; + $this->runnerManager = $runnerManager; + $this->results = $resultAggregator; + $this->eventDispatcher = $eventDispatcher; + $this->checkRepository = $checkRepository; } /** @@ -76,10 +76,10 @@ public function __construct( * the check specified as the first argument will also be executed. Throws an `InvalidArgumentException` * if the check does not exist in the `CheckRepository`. * - * @param string $requiredCheck The name of the required check. + * @param class-string $requiredCheck The name of the required check. * @throws InvalidArgumentException If the check does not exist. */ - public function requireCheck($requiredCheck) + public function requireCheck(string $requiredCheck): void { if (!$this->checkRepository->has($requiredCheck)) { throw new InvalidArgumentException(sprintf('Check: "%s" does not exist', $requiredCheck)); @@ -124,7 +124,7 @@ public function requireCheck($requiredCheck) * @throws ExerciseNotConfiguredException If the exercise does not implement the correct interface based on * the checks required. */ - public function verify(ExerciseInterface $exercise, Input $input) + public function verify(ExerciseInterface $exercise, Input $input): ResultAggregator { $exercise->configure($this); @@ -176,7 +176,7 @@ public function verify(ExerciseInterface $exercise, Input $input) * @param OutputInterface $output An output instance capable of writing to stdout. * @return bool Whether the solution ran successfully or not. */ - public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output) + public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output): bool { $exercise->configure($this); $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('run.start', $exercise, $input)); @@ -193,12 +193,12 @@ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $ } /** - * @param CheckInterface[] $checks + * @param SimpleCheckInterface[] $checks * @param ExerciseInterface $exercise * @throws CheckNotApplicableException * @throws ExerciseNotConfiguredException */ - private function validateChecks(array $checks, ExerciseInterface $exercise) + private function validateChecks(array $checks, ExerciseInterface $exercise): void { foreach ($checks as $check) { if (!$check->canRun($exercise->getType())) { @@ -217,7 +217,7 @@ private function validateChecks(array $checks, ExerciseInterface $exercise) * * @return EventDispatcher */ - public function getEventDispatcher() + public function getEventDispatcher(): EventDispatcher { return $this->eventDispatcher; } diff --git a/src/ExerciseRenderer.php b/src/ExerciseRenderer.php index 622e6555..ba1c75f5 100644 --- a/src/ExerciseRenderer.php +++ b/src/ExerciseRenderer.php @@ -77,7 +77,7 @@ public function __construct( /** * @param CliMenu $menu */ - public function __invoke(CliMenu $menu) + public function __invoke(CliMenu $menu): void { $menu->close(); @@ -89,7 +89,7 @@ public function __invoke(CliMenu $menu) $this->userStateSerializer->serialize($this->userState); $numExercises = count($exercises); - $exerciseIndex = array_search($exercise, $exercises) + 1; + $exerciseIndex = ((int) array_search($exercise, $exercises)) + 1; $output = "\n"; $output .= $this->color->__invoke(' LEARN YOU THE PHP FOR MUCH WIN! ')->magenta()->bold() . "\n"; @@ -98,7 +98,7 @@ public function __invoke(CliMenu $menu) $output .= $this->color->__invoke(" " . $exercise->getName())->yellow()->bold() . "\n"; $output .= $this->color->__invoke(sprintf(" Exercise %d of %d\n\n", $exerciseIndex, $numExercises))->yellow(); - $content = file_get_contents($exercise->getProblem()); + $content = (string) file_get_contents($exercise->getProblem()); $doc = $this->markdownRenderer->render($content); $doc = str_replace('{appname}', $this->appName, $doc); $output .= $doc; @@ -118,7 +118,7 @@ public function __invoke(CliMenu $menu) * @param string $cmd * @return string */ - private function helpLine($text, $cmd) + private function helpLine(string $text, string $cmd): string { $cmd = $this->color->__invoke(sprintf('%s %s', $this->appName, $cmd))->yellow()->__toString(); return sprintf( diff --git a/src/ExerciseRepository.php b/src/ExerciseRepository.php index b9210f84..4438d82e 100644 --- a/src/ExerciseRepository.php +++ b/src/ExerciseRepository.php @@ -10,6 +10,8 @@ /** * Exercise repository, use to locate individual/all exercises by certain criteria. + * + * @implements IteratorAggregate */ class ExerciseRepository implements IteratorAggregate, Countable { @@ -21,7 +23,7 @@ class ExerciseRepository implements IteratorAggregate, Countable /** * Requires an array of `ExerciseInterface` instances. * - * @param ExerciseInterface[] $exercises + * @param array $exercises */ public function __construct(array $exercises) { @@ -34,7 +36,7 @@ public function __construct(array $exercises) * @param ExerciseInterface $exercise * @return ExerciseInterface */ - private function validateExercise(ExerciseInterface $exercise) + private function validateExercise(ExerciseInterface $exercise): ExerciseInterface { $type = $exercise->getType(); @@ -50,9 +52,9 @@ private function validateExercise(ExerciseInterface $exercise) /** * Retrieve all of the exercises as an array. * - * @return ExerciseInterface[] + * @return array */ - public function findAll() + public function findAll(): array { return $this->exercises; } @@ -65,7 +67,7 @@ public function findAll() * @return ExerciseInterface * @throws InvalidArgumentException */ - public function findByName($name) + public function findByName(string $name): ExerciseInterface { foreach ($this->exercises as $exercise) { if ($name === $exercise->getName()) { @@ -79,9 +81,9 @@ public function findByName($name) /** * Get the names of each exercise as an array. * - * @return array + * @return array */ - public function getAllNames() + public function getAllNames(): array { return array_map(function (ExerciseInterface $exercise) { return $exercise->getName(); @@ -93,7 +95,7 @@ public function getAllNames() * * @return int */ - public function count() + public function count(): int { return count($this->exercises); } @@ -101,9 +103,9 @@ public function count() /** * Allow to iterate over the repository with `foreach`. * - * @return ArrayIterator + * @return ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->exercises); } diff --git a/src/ExerciseRunner/CgiRunner.php b/src/ExerciseRunner/CgiRunner.php index 898dabe3..0d637f6b 100644 --- a/src/ExerciseRunner/CgiRunner.php +++ b/src/ExerciseRunner/CgiRunner.php @@ -12,6 +12,7 @@ use PhpSchool\PhpWorkshop\Exception\CodeExecutionException; use PhpSchool\PhpWorkshop\Exception\SolutionExecutionException; use PhpSchool\PhpWorkshop\Exercise\CgiExercise; +use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult; @@ -32,9 +33,8 @@ */ class CgiRunner implements ExerciseRunnerInterface { - /** - * @var CgiExercise + * @var CgiExercise&ExerciseInterface */ private $exercise; @@ -49,7 +49,7 @@ class CgiRunner implements ExerciseRunnerInterface private $requestRenderer; /** - * @var array + * @var array */ private static $requiredChecks = [ FileExistsCheck::class, @@ -93,6 +93,7 @@ public function __construct( ); } } + /** @var CgiExercise&ExerciseInterface $exercise */ $this->eventDispatcher = $eventDispatcher; $this->exercise = $exercise; $this->requestRenderer = $requestRenderer; @@ -101,7 +102,7 @@ public function __construct( /** * @return string */ - public function getName() + public function getName(): string { return 'CGI Program Runner'; } @@ -109,9 +110,9 @@ public function getName() /** * Get an array of the class names of the required checks this runner needs. * - * @return array + * @return array */ - public function getRequiredChecks() + public function getRequiredChecks(): array { return static::$requiredChecks; } @@ -121,9 +122,10 @@ public function getRequiredChecks() * @param string $fileName * @return ResultInterface */ - private function checkRequest(RequestInterface $request, $fileName) + private function checkRequest(RequestInterface $request, string $fileName): ResultInterface { try { + /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( new CgiExecuteEvent('cgi.verify.reference-execute.pre', $request) ); @@ -138,6 +140,7 @@ private function checkRequest(RequestInterface $request, $fileName) } try { + /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.verify.student-execute.pre', $request)); $userResponse = $this->executePhpFile($fileName, $event->getRequest(), 'student'); } catch (CodeExecutionException $e) { @@ -159,9 +162,9 @@ private function checkRequest(RequestInterface $request, $fileName) /** * @param ResponseInterface $response - * @return array + * @return array */ - private function getHeaders(ResponseInterface $response) + private function getHeaders(ResponseInterface $response): array { $headers = []; foreach ($response->getHeaders() as $name => $values) { @@ -176,7 +179,7 @@ private function getHeaders(ResponseInterface $response) * @param string $type * @return ResponseInterface */ - private function executePhpFile($fileName, RequestInterface $request, $type) + private function executePhpFile(string $fileName, RequestInterface $request, string $type): ResponseInterface { $process = $this->getProcess($fileName, $request); @@ -202,7 +205,7 @@ private function executePhpFile($fileName, RequestInterface $request, $type) * @param RequestInterface $request * @return Process */ - private function getProcess($fileName, RequestInterface $request) + private function getProcess(string $fileName, RequestInterface $request): Process { $env = [ 'REQUEST_METHOD' => $request->getMethod(), @@ -247,13 +250,13 @@ private function getProcess($fileName, RequestInterface $request) * @param Input $input The command line arguments passed to the command. * @return CgiResult The result of the check. */ - public function verify(Input $input) + public function verify(Input $input): ResultInterface { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.verify.start', $this->exercise, $input)); $result = new CgiResult( array_map( function (RequestInterface $request) use ($input) { - return $this->checkRequest($request, $input->getArgument('program')); + return $this->checkRequest($request, $input->getRequiredArgument('program')); }, $this->exercise->getRequests() ) @@ -279,15 +282,16 @@ function (RequestInterface $request) use ($input) { * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output) + public function run(Input $input, OutputInterface $output): bool { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.run.start', $this->exercise, $input)); $success = true; foreach ($this->exercise->getRequests() as $i => $request) { + /** @var CgiExecuteEvent $event */ $event = $this->eventDispatcher->dispatch( new CgiExecuteEvent('cgi.run.student-execute.pre', $request) ); - $process = $this->getProcess($input->getArgument('program'), $event->getRequest()); + $process = $this->getProcess($input->getRequiredArgument('program'), $event->getRequest()); $output->writeTitle("Request"); $output->emptyLine(); diff --git a/src/ExerciseRunner/CliRunner.php b/src/ExerciseRunner/CliRunner.php index 2da9654a..bf5728e9 100644 --- a/src/ExerciseRunner/CliRunner.php +++ b/src/ExerciseRunner/CliRunner.php @@ -12,6 +12,7 @@ use PhpSchool\PhpWorkshop\Exception\CodeExecutionException; use PhpSchool\PhpWorkshop\Exception\SolutionExecutionException; use PhpSchool\PhpWorkshop\Exercise\CliExercise; +use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure; @@ -33,7 +34,7 @@ class CliRunner implements ExerciseRunnerInterface { /** - * @var CliExercise + * @var CliExercise&ExerciseInterface */ private $exercise; @@ -43,7 +44,7 @@ class CliRunner implements ExerciseRunnerInterface private $eventDispatcher; /** - * @var array + * @var array */ private static $requiredChecks = [ FileExistsCheck::class, @@ -59,6 +60,7 @@ class CliRunner implements ExerciseRunnerInterface */ public function __construct(CliExercise $exercise, EventDispatcher $eventDispatcher) { + /** @var CliExercise&ExerciseInterface $exercise */ $this->eventDispatcher = $eventDispatcher; $this->exercise = $exercise; } @@ -66,7 +68,7 @@ public function __construct(CliExercise $exercise, EventDispatcher $eventDispatc /** * @return string */ - public function getName() + public function getName(): string { return 'CLI Program Runner'; } @@ -74,20 +76,20 @@ public function getName() /** * Get an array of the class names of the required checks this runner needs. * - * @return array + * @return array */ - public function getRequiredChecks() + public function getRequiredChecks(): array { return static::$requiredChecks; } /** * @param string $fileName - * @param ArrayObject $args + * @param ArrayObject $args * @param string $type * @return string */ - private function executePhpFile($fileName, ArrayObject $args, $type) + private function executePhpFile(string $fileName, ArrayObject $args, string $type): string { $process = $this->getPhpProcess($fileName, $args); @@ -104,11 +106,11 @@ private function executePhpFile($fileName, ArrayObject $args, $type) /** * @param string $fileName - * @param ArrayObject $args + * @param ArrayObject $args * * @return Process */ - private function getPhpProcess($fileName, ArrayObject $args) + private function getPhpProcess(string $fileName, ArrayObject $args): Process { return new Process( $args->prepend($fileName)->prepend(PHP_BINARY)->getArrayCopy(), @@ -135,7 +137,7 @@ private function getPhpProcess($fileName, ArrayObject $args) * @param Input $input The command line arguments passed to the command. * @return CliResult The result of the check. */ - public function verify(Input $input) + public function verify(Input $input): ResultInterface { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cli.verify.start', $this->exercise, $input)); $result = new CliResult( @@ -153,14 +155,14 @@ function (array $args) use ($input) { /** * BC - getArgs only returned 1 set of args in v1 instead of multiple sets of args in v2 * - * @param array $args - * @return array + * @param array>|array $args + * @return array> */ - private function preserveOldArgFormat(array $args) + private function preserveOldArgFormat(array $args): array { if (isset($args[0]) && !is_array($args[0])) { $args = [$args]; - } elseif (empty($args)) { + } elseif (count($args) === 0) { $args = [[]]; } @@ -168,16 +170,17 @@ private function preserveOldArgFormat(array $args) } /** - * @param array $args + * @param array $args * @param Input $input * @return ResultInterface */ - private function doVerify(array $args, Input $input) + private function doVerify(array $args, Input $input): ResultInterface { //arrays are not pass-by-ref $args = new ArrayObject($args); try { + /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.verify.reference-execute.pre', $args)); $solutionOutput = $this->executePhpFile( $this->exercise->getSolution()->getEntryPoint(), @@ -190,8 +193,9 @@ private function doVerify(array $args, Input $input) } try { + /** @var CliExecuteEvent $event */ $event = $this->eventDispatcher->dispatch(new CliExecuteEvent('cli.verify.student-execute.pre', $args)); - $userOutput = $this->executePhpFile($input->getArgument('program'), $event->getArgs(), 'student'); + $userOutput = $this->executePhpFile($input->getRequiredArgument('program'), $event->getArgs(), 'student'); } catch (CodeExecutionException $e) { $this->eventDispatcher->dispatch(new Event('cli.verify.student-execute.fail', ['exception' => $e])); return GenericFailure::fromArgsAndCodeExecutionFailure($args, $e); @@ -219,7 +223,7 @@ private function doVerify(array $args, Input $input) * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output) + public function run(Input $input, OutputInterface $output): bool { $this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cli.run.start', $this->exercise, $input)); $success = true; @@ -240,7 +244,7 @@ public function run(Input $input, OutputInterface $output) } $output->writeTitle("Output"); - $process = $this->getPhpProcess($input->getArgument('program'), $args); + $process = $this->getPhpProcess($input->getRequiredArgument('program'), $args); $process->start(); $this->eventDispatcher->dispatch( new CliExecuteEvent('cli.run.student.executing', $args, ['output' => $output]) diff --git a/src/ExerciseRunner/CustomVerifyingRunner.php b/src/ExerciseRunner/CustomVerifyingRunner.php index 9680ba72..65297896 100644 --- a/src/ExerciseRunner/CustomVerifyingRunner.php +++ b/src/ExerciseRunner/CustomVerifyingRunner.php @@ -30,7 +30,7 @@ public function __construct(CustomVerifyingExercise $exercise) * * @return string */ - public function getName() + public function getName(): string { return 'Custom Verifying Runner'; } @@ -38,9 +38,9 @@ public function getName() /** * Get an array of the class names of the required checks this runner needs. * - * @return array + * @return array */ - public function getRequiredChecks() + public function getRequiredChecks(): array { return []; } @@ -52,7 +52,7 @@ public function getRequiredChecks() * @param Input $input The command line arguments passed to the command. * @return ResultInterface The result of the check. */ - public function verify(Input $input) + public function verify(Input $input): ResultInterface { return $this->exercise->verify(); } @@ -65,7 +65,7 @@ public function verify(Input $input) * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output) + public function run(Input $input, OutputInterface $output): bool { $message = 'Nothing to run here. This exercise does not require a code solution, '; $message .= 'so there is nothing to execute.'; diff --git a/src/ExerciseRunner/ExerciseRunnerInterface.php b/src/ExerciseRunner/ExerciseRunnerInterface.php index 27b599ff..8a94af4f 100644 --- a/src/ExerciseRunner/ExerciseRunnerInterface.php +++ b/src/ExerciseRunner/ExerciseRunnerInterface.php @@ -19,14 +19,14 @@ interface ExerciseRunnerInterface * * @return string */ - public function getName(); + public function getName(): string; /** * Get an array of the class names of the required checks this runner needs. * - * @return array + * @return array */ - public function getRequiredChecks(); + public function getRequiredChecks(): array; /** * Verify a solution to an exercise. Verification involves executing the reference solution @@ -41,7 +41,7 @@ public function getRequiredChecks(); * @param Input $input The command line arguments passed to the command. * @return ResultInterface The result of the check. */ - public function verify(Input $input); + public function verify(Input $input): ResultInterface; /** * Run a solution to an exercise. This simply run's the student's solution with the correct input from the exercise @@ -52,5 +52,5 @@ public function verify(Input $input); * @param OutputInterface $output A wrapper around STDOUT. * @return bool If the solution was successfully executed, eg. exit code was 0. */ - public function run(Input $input, OutputInterface $output); + public function run(Input $input, OutputInterface $output): bool; } diff --git a/src/ExerciseRunner/Factory/CgiRunnerFactory.php b/src/ExerciseRunner/Factory/CgiRunnerFactory.php index eeb262a2..56a8a50d 100644 --- a/src/ExerciseRunner/Factory/CgiRunnerFactory.php +++ b/src/ExerciseRunner/Factory/CgiRunnerFactory.php @@ -5,6 +5,7 @@ use PhpSchool\PhpWorkshop\CommandArgument; use PhpSchool\PhpWorkshop\CommandDefinition; use PhpSchool\PhpWorkshop\Event\EventDispatcher; +use PhpSchool\PhpWorkshop\Exercise\CgiExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner; @@ -46,7 +47,7 @@ public function __construct(EventDispatcher $eventDispatcher, RequestRenderer $r * @param ExerciseInterface $exercise * @return bool */ - public function supports(ExerciseInterface $exercise) + public function supports(ExerciseInterface $exercise): bool { return $exercise->getType()->getValue() === self::$type; } @@ -56,7 +57,7 @@ public function supports(ExerciseInterface $exercise) * * @param CommandDefinition $commandDefinition */ - public function configureInput(CommandDefinition $commandDefinition) + public function configureInput(CommandDefinition $commandDefinition): void { $commandDefinition->addArgument(CommandArgument::required('program')); } @@ -64,10 +65,10 @@ public function configureInput(CommandDefinition $commandDefinition) /** * Create and return an instance of the runner. * - * @param ExerciseInterface $exercise + * @param ExerciseInterface&CgiExercise $exercise * @return ExerciseRunnerInterface */ - public function create(ExerciseInterface $exercise) + public function create(ExerciseInterface $exercise): ExerciseRunnerInterface { return new CgiRunner($exercise, $this->eventDispatcher, $this->requestRenderer); } diff --git a/src/ExerciseRunner/Factory/CliRunnerFactory.php b/src/ExerciseRunner/Factory/CliRunnerFactory.php index 2dc19636..0461afdf 100644 --- a/src/ExerciseRunner/Factory/CliRunnerFactory.php +++ b/src/ExerciseRunner/Factory/CliRunnerFactory.php @@ -5,6 +5,7 @@ use PhpSchool\PhpWorkshop\CommandArgument; use PhpSchool\PhpWorkshop\CommandDefinition; use PhpSchool\PhpWorkshop\Event\EventDispatcher; +use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner; @@ -39,7 +40,7 @@ public function __construct(EventDispatcher $eventDispatcher) * @param ExerciseInterface $exercise * @return bool */ - public function supports(ExerciseInterface $exercise) + public function supports(ExerciseInterface $exercise): bool { return $exercise->getType()->getValue() === self::$type; } @@ -49,7 +50,7 @@ public function supports(ExerciseInterface $exercise) * * @param CommandDefinition $commandDefinition */ - public function configureInput(CommandDefinition $commandDefinition) + public function configureInput(CommandDefinition $commandDefinition): void { $commandDefinition->addArgument(CommandArgument::required('program')); } @@ -57,10 +58,10 @@ public function configureInput(CommandDefinition $commandDefinition) /** * Create and return an instance of the runner. * - * @param ExerciseInterface $exercise + * @param ExerciseInterface&CliExercise $exercise * @return ExerciseRunnerInterface */ - public function create(ExerciseInterface $exercise) + public function create(ExerciseInterface $exercise): ExerciseRunnerInterface { return new CliRunner($exercise, $this->eventDispatcher); } diff --git a/src/ExerciseRunner/Factory/CustomVerifyingRunnerFactory.php b/src/ExerciseRunner/Factory/CustomVerifyingRunnerFactory.php index fe58676d..b3d5de18 100644 --- a/src/ExerciseRunner/Factory/CustomVerifyingRunnerFactory.php +++ b/src/ExerciseRunner/Factory/CustomVerifyingRunnerFactory.php @@ -3,6 +3,7 @@ namespace PhpSchool\PhpWorkshop\ExerciseRunner\Factory; use PhpSchool\PhpWorkshop\CommandDefinition; +use PhpSchool\PhpWorkshop\Exercise\CustomVerifyingExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\ExerciseRunner\CustomVerifyingRunner; @@ -24,7 +25,7 @@ class CustomVerifyingRunnerFactory implements ExerciseRunnerFactoryInterface * @param ExerciseInterface $exercise * @return bool */ - public function supports(ExerciseInterface $exercise) + public function supports(ExerciseInterface $exercise): bool { return $exercise->getType()->getValue() === self::$type; } @@ -34,17 +35,17 @@ public function supports(ExerciseInterface $exercise) * * @param CommandDefinition $commandDefinition */ - public function configureInput(CommandDefinition $commandDefinition) + public function configureInput(CommandDefinition $commandDefinition): void { } /** * Create and return an instance of the runner. * - * @param ExerciseInterface $exercise + * @param ExerciseInterface&CustomVerifyingExercise $exercise * @return ExerciseRunnerInterface */ - public function create(ExerciseInterface $exercise) + public function create(ExerciseInterface $exercise): ExerciseRunnerInterface { return new CustomVerifyingRunner($exercise); } diff --git a/src/ExerciseRunner/Factory/ExerciseRunnerFactoryInterface.php b/src/ExerciseRunner/Factory/ExerciseRunnerFactoryInterface.php index 49b76d7c..683023d0 100644 --- a/src/ExerciseRunner/Factory/ExerciseRunnerFactoryInterface.php +++ b/src/ExerciseRunner/Factory/ExerciseRunnerFactoryInterface.php @@ -17,14 +17,14 @@ interface ExerciseRunnerFactoryInterface * @param ExerciseInterface $exercise * @return bool */ - public function supports(ExerciseInterface $exercise); + public function supports(ExerciseInterface $exercise): bool; /** * Add any extra required arguments to the command. * * @param CommandDefinition $commandDefinition */ - public function configureInput(CommandDefinition $commandDefinition); + public function configureInput(CommandDefinition $commandDefinition): void; /** * Create and return an instance of the runner. @@ -32,5 +32,5 @@ public function configureInput(CommandDefinition $commandDefinition); * @param ExerciseInterface $exercise * @return ExerciseRunnerInterface */ - public function create(ExerciseInterface $exercise); + public function create(ExerciseInterface $exercise): ExerciseRunnerInterface; } diff --git a/src/ExerciseRunner/RunnerManager.php b/src/ExerciseRunner/RunnerManager.php index 3e4a4d52..9596dfd0 100644 --- a/src/ExerciseRunner/RunnerManager.php +++ b/src/ExerciseRunner/RunnerManager.php @@ -20,7 +20,7 @@ class RunnerManager /** * @param ExerciseRunnerFactoryInterface $factory */ - public function addFactory(ExerciseRunnerFactoryInterface $factory) + public function addFactory(ExerciseRunnerFactoryInterface $factory): void { $this->factories[] = $factory; } @@ -29,7 +29,7 @@ public function addFactory(ExerciseRunnerFactoryInterface $factory) * @param ExerciseInterface $exercise * @param CommandDefinition $commandDefinition */ - public function configureInput(ExerciseInterface $exercise, CommandDefinition $commandDefinition) + public function configureInput(ExerciseInterface $exercise, CommandDefinition $commandDefinition): void { $this->getFactory($exercise)->configureInput($commandDefinition); } @@ -38,7 +38,7 @@ public function configureInput(ExerciseInterface $exercise, CommandDefinition $c * @param ExerciseInterface $exercise * @return ExerciseRunnerInterface */ - public function getRunner(ExerciseInterface $exercise) + public function getRunner(ExerciseInterface $exercise): ExerciseRunnerInterface { return $this->getFactory($exercise)->create($exercise); } @@ -48,7 +48,7 @@ public function getRunner(ExerciseInterface $exercise) * @return ExerciseRunnerFactoryInterface * @throws InvalidArgumentException */ - private function getFactory(ExerciseInterface $exercise) + private function getFactory(ExerciseInterface $exercise): ExerciseRunnerFactoryInterface { foreach ($this->factories as $factory) { if ($factory->supports($exercise)) { diff --git a/src/Factory/EventDispatcherFactory.php b/src/Factory/EventDispatcherFactory.php index 320e9246..16d0303c 100644 --- a/src/Factory/EventDispatcherFactory.php +++ b/src/Factory/EventDispatcherFactory.php @@ -14,13 +14,12 @@ */ class EventDispatcherFactory { - /** * @param ContainerInterface $container * @return EventDispatcher * @throws InvalidArgumentException */ - public function __invoke(ContainerInterface $container) + public function __invoke(ContainerInterface $container): EventDispatcher { $dispatcher = new EventDispatcher($container->get(ResultAggregator::class)); @@ -47,10 +46,10 @@ public function __invoke(ContainerInterface $container) } /** - * @param array $listeners - * @return array + * @param array> $listeners + * @return array */ - private function mergeListenerGroups(array $listeners) + private function mergeListenerGroups(array $listeners): array { $listeners = new Collection($listeners); @@ -79,17 +78,17 @@ private function mergeListenerGroups(array $listeners) /** * @param string $eventName - * @param array $listeners + * @param array $listeners * @param ContainerInterface $container * @param EventDispatcher $dispatcher - * @throws \PhpSchool\PhpWorkshop\Exception\InvalidArgumentException + * @throws InvalidArgumentException */ private function attachListeners( - $eventName, + string $eventName, array $listeners, ContainerInterface $container, EventDispatcher $dispatcher - ) { + ): void { array_walk($listeners, function ($listener) use ($eventName, $dispatcher, $container) { if ($listener instanceof ContainerListenerHelper) { if (!$container->has($listener->getService())) { @@ -98,7 +97,7 @@ private function attachListeners( ); } - return $dispatcher->listen($eventName, function (...$args) use ($container, $listener) { + $dispatcher->listen($eventName, function (...$args) use ($container, $listener) { $service = $container->get($listener->getService()); if (!method_exists($service, $listener->getMethod())) { @@ -109,6 +108,7 @@ private function attachListeners( $service->{$listener->getMethod()}(...$args); }); + return; } if (!is_callable($listener)) { @@ -116,8 +116,7 @@ private function attachListeners( sprintf('Listener must be a callable or a container entry for a callable service.') ); } - - return $dispatcher->listen($eventName, $listener); + $dispatcher->listen($eventName, $listener); }); } } diff --git a/src/Factory/MenuFactory.php b/src/Factory/MenuFactory.php index b02f2382..e684816d 100644 --- a/src/Factory/MenuFactory.php +++ b/src/Factory/MenuFactory.php @@ -142,7 +142,7 @@ private function isExerciseDisabled(ExerciseInterface $exercise, UserState $user * @param EventDispatcher $eventDispatcher * @param ExerciseInterface $exercise */ - private function dispatchExerciseSelectedEvent(EventDispatcher $eventDispatcher, ExerciseInterface $exercise) + private function dispatchExerciseSelectedEvent(EventDispatcher $eventDispatcher, ExerciseInterface $exercise): void { $eventDispatcher->dispatch( new Event( diff --git a/src/Factory/ResultRendererFactory.php b/src/Factory/ResultRendererFactory.php index df3fd791..fb9f89ac 100644 --- a/src/Factory/ResultRendererFactory.php +++ b/src/Factory/ResultRendererFactory.php @@ -12,21 +12,21 @@ class ResultRendererFactory { /** - * @var array + * @var array */ private $mappings = []; /** - * @var array + * @var array */ private $factories = []; /** - * @param string $resultClass - * @param string $rendererClass - * @param callable $factory + * @param class-string $resultClass + * @param class-string $rendererClass + * @param callable|null $factory */ - public function registerRenderer($resultClass, $rendererClass, callable $factory = null) + public function registerRenderer(string $resultClass, string $rendererClass, callable $factory = null): void { if (!$this->isImplementationNameOfClass($resultClass, ResultInterface::class)) { throw new InvalidArgumentException(); @@ -47,7 +47,7 @@ public function registerRenderer($resultClass, $rendererClass, callable $factory * @param ResultInterface $result * @return ResultRendererInterface */ - public function create(ResultInterface $result) + public function create(ResultInterface $result): ResultRendererInterface { $class = get_class($result); if (!isset($this->mappings[$class])) { @@ -70,11 +70,17 @@ public function create(ResultInterface $result) ); } + /** @var ResultRendererInterface $renderer */ return $renderer; } - protected function isImplementationNameOfClass($implementationName, $className) + /** + * @param class-string $implementationName + * @param class-string $className + * @return bool + */ + protected function isImplementationNameOfClass(string $implementationName, string $className): bool { - return is_string($implementationName) && is_subclass_of($implementationName, $className); + return is_subclass_of($implementationName, $className); } } diff --git a/src/Input/Input.php b/src/Input/Input.php index 9b2bec9b..192534f8 100644 --- a/src/Input/Input.php +++ b/src/Input/Input.php @@ -16,16 +16,15 @@ class Input private $appName; /** - * @var array + * @var array */ private $arguments = []; /** - * Input constructor. - * @param $appName - * @param array $arguments + * @param string $appName + * @param array $arguments */ - public function __construct($appName, array $arguments = []) + public function __construct(string $appName, array $arguments = []) { $this->appName = $appName; $this->arguments = $arguments; @@ -34,7 +33,7 @@ public function __construct($appName, array $arguments = []) /** * @return string */ - public function getAppName() + public function getAppName(): string { return $this->appName; } @@ -43,17 +42,17 @@ public function getAppName() * @param string $name * @return bool */ - public function hasArgument($name) + public function hasArgument(string $name): bool { - return isset($this->arguments[$name]); + return array_key_exists($name, $this->arguments); } /** * @param string $name - * @return string + * @return ?string * @throws InvalidArgumentException */ - public function getArgument($name) + public function getArgument(string $name): ?string { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(sprintf('Argument with name: "%s" does not exist', $name)); @@ -62,11 +61,27 @@ public function getArgument($name) return $this->arguments[$name]; } + /** + * @param string $name + * @return string + * @throws InvalidArgumentException + */ + public function getRequiredArgument(string $name): string + { + $arg = $this->getArgument($name); + + if (null === $arg) { + throw new InvalidArgumentException(sprintf('Argument with name: "%s" is required', $name)); + } + + return $arg; + } + /** * @param string $name * @param string $value */ - public function setArgument($name, $value) + public function setArgument(string $name, string $value): void { $this->arguments[$name] = $value; } diff --git a/src/Listener/CheckExerciseAssignedListener.php b/src/Listener/CheckExerciseAssignedListener.php index a4169dff..d7750ddb 100644 --- a/src/Listener/CheckExerciseAssignedListener.php +++ b/src/Listener/CheckExerciseAssignedListener.php @@ -24,7 +24,7 @@ public function __construct(UserState $userState) /** * @param Event $event */ - public function __invoke(Event $event) + public function __invoke(Event $event): void { /** @var CommandDefinition $command */ $command = $event->getParameter('command'); diff --git a/src/Listener/CodePatchListener.php b/src/Listener/CodePatchListener.php index 01bd57cb..b38e7d5c 100644 --- a/src/Listener/CodePatchListener.php +++ b/src/Listener/CodePatchListener.php @@ -34,11 +34,15 @@ public function __construct(CodePatcher $codePatcher) /** * @param ExerciseRunnerEvent $event */ - public function patch(ExerciseRunnerEvent $event) + public function patch(ExerciseRunnerEvent $event): void { $fileName = $event->getInput()->getArgument('program'); - $this->originalCode = file_get_contents($fileName); + if (null === $fileName) { + return; + } + + $this->originalCode = (string) file_get_contents($fileName); file_put_contents( $fileName, $this->codePatcher->patch($event->getExercise(), $this->originalCode) @@ -48,12 +52,18 @@ public function patch(ExerciseRunnerEvent $event) /** * @param ExerciseRunnerEvent $event */ - public function revert(ExerciseRunnerEvent $event) + public function revert(ExerciseRunnerEvent $event): void { if (null === $this->originalCode) { throw new RuntimeException('Can only revert previously patched code'); } - file_put_contents($event->getInput()->getArgument('program'), $this->originalCode); + $fileName = $event->getInput()->getArgument('program'); + + if (null === $fileName) { + return; + } + + file_put_contents($fileName, $this->originalCode); } } diff --git a/src/Listener/ConfigureCommandListener.php b/src/Listener/ConfigureCommandListener.php index 8c551c35..cc032bf5 100644 --- a/src/Listener/ConfigureCommandListener.php +++ b/src/Listener/ConfigureCommandListener.php @@ -46,7 +46,7 @@ public function __construct( /** * @param Event $event */ - public function __invoke(Event $event) + public function __invoke(Event $event): void { /** @var CommandDefinition $command */ $command = $event->getParameter('command'); diff --git a/src/Listener/PrepareSolutionListener.php b/src/Listener/PrepareSolutionListener.php index c5b58eec..0095fa4f 100644 --- a/src/Listener/PrepareSolutionListener.php +++ b/src/Listener/PrepareSolutionListener.php @@ -3,6 +3,7 @@ namespace PhpSchool\PhpWorkshop\Listener; use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent; +use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; use RuntimeException; use Symfony\Component\Process\Process; @@ -14,7 +15,7 @@ class PrepareSolutionListener /** * Locations for composer executable * - * @var array + * @var array */ private $composerLocations = [ 'composer', @@ -26,9 +27,15 @@ class PrepareSolutionListener /** * @param ExerciseRunnerEvent $event */ - public function __invoke(ExerciseRunnerEvent $event) + public function __invoke(ExerciseRunnerEvent $event): void { - $solution = $event->getExercise()->getSolution(); + $exercise = $event->getExercise(); + + if (!$exercise instanceof ProvidesSolution) { + return; + } + + $solution = $exercise->getSolution(); if ($solution->hasComposerFile()) { //prepare composer deps diff --git a/src/Listener/RealPathListener.php b/src/Listener/RealPathListener.php index 5ee6eaa8..2d8b8207 100644 --- a/src/Listener/RealPathListener.php +++ b/src/Listener/RealPathListener.php @@ -17,16 +17,16 @@ class RealPathListener /** * @param ExerciseRunnerEvent $event */ - public function __invoke(ExerciseRunnerEvent $event) + public function __invoke(ExerciseRunnerEvent $event): void { if (!$event->getInput()->hasArgument('program')) { return; } - $program = $event->getInput()->getArgument('program'); + $program = $event->getInput()->getRequiredArgument('program'); if (file_exists($program)) { - $event->getInput()->setArgument('program', realpath($program)); + $event->getInput()->setArgument('program', (string) realpath($program)); } } } diff --git a/src/Listener/SelfCheckListener.php b/src/Listener/SelfCheckListener.php index ace5a0e7..e0773a92 100644 --- a/src/Listener/SelfCheckListener.php +++ b/src/Listener/SelfCheckListener.php @@ -27,7 +27,7 @@ public function __construct(ResultAggregator $results) /** * @param Event $event */ - public function __invoke(Event $event) + public function __invoke(Event $event): void { $exercise = $event->getParameter('exercise'); diff --git a/src/MenuItem/ResetProgress.php b/src/MenuItem/ResetProgress.php index f7d0517f..20399f5a 100644 --- a/src/MenuItem/ResetProgress.php +++ b/src/MenuItem/ResetProgress.php @@ -27,13 +27,17 @@ public function __construct(UserStateSerializer $userStateSerializer) /** * @param CliMenu $menu */ - public function __invoke(CliMenu $menu) + public function __invoke(CliMenu $menu): void { $this->userStateSerializer->serialize(new UserState()); - $items = $menu - ->getParent() - ->getItems(); + $parent = $menu->getParent(); + + if (!$parent) { + return; + } + + $items = $parent->getItems(); foreach ($items as $item) { $item->hideItemExtra(); diff --git a/src/NodeVisitor/FunctionVisitor.php b/src/NodeVisitor/FunctionVisitor.php index 6889cfa9..6db8540d 100644 --- a/src/NodeVisitor/FunctionVisitor.php +++ b/src/NodeVisitor/FunctionVisitor.php @@ -12,27 +12,27 @@ class FunctionVisitor extends NodeVisitorAbstract { /** - * @var array + * @var array */ private $requiredFunctions; /** - * @var array + * @var array */ private $bannedFunctions; /** - * @var array + * @var array */ private $requiredUsages = []; /** - * @var array + * @var array */ private $bannedUsages = []; /** - * @param array $requiredFunctions - * @param array $bannedFunctions + * @param array $requiredFunctions + * @param array $bannedFunctions */ public function __construct(array $requiredFunctions, array $bannedFunctions) { @@ -42,41 +42,44 @@ public function __construct(array $requiredFunctions, array $bannedFunctions) /** * @param Node $node + * @return null */ public function leaveNode(Node $node) { - if ($node instanceof FuncCall) { + if ($node instanceof FuncCall && $node->name instanceof Node\Name) { $name = $node->name->__toString(); - if (in_array($name, $this->requiredFunctions)) { + if (in_array($name, $this->requiredFunctions, true)) { $this->requiredUsages[] = $node; } - if (in_array($name, $this->bannedFunctions)) { + if (in_array($name, $this->bannedFunctions, true)) { $this->bannedUsages[] = $node; } } + + return null; } /** * @return bool */ - public function hasUsedBannedFunctions() + public function hasUsedBannedFunctions(): bool { return count($this->bannedUsages) > 0; } /** - * @return array + * @return array */ - public function getBannedUsages() + public function getBannedUsages(): array { return $this->bannedUsages; } /** - * @return array + * @return array */ - public function getRequiredUsages() + public function getRequiredUsages(): array { return $this->requiredUsages; } @@ -84,10 +87,14 @@ public function getRequiredUsages() /** * @return bool */ - public function hasMetFunctionRequirements() + public function hasMetFunctionRequirements(): bool { $metRequires = array_filter($this->requiredFunctions, function ($function) { foreach ($this->getRequiredUsages() as $usage) { + if (!$usage->name instanceof Node\Name) { + continue; + } + if ($usage->name->__toString() === $function) { return true; } @@ -99,12 +106,16 @@ public function hasMetFunctionRequirements() } /** - * @return array + * @return array */ - public function getMissingRequirements() + public function getMissingRequirements(): array { return array_filter($this->requiredFunctions, function ($function) { foreach ($this->getRequiredUsages() as $usage) { + if (!$usage->name instanceof Node\Name) { + continue; + } + if ($usage->name->__toString() === $function) { return false; } diff --git a/src/Output/OutputInterface.php b/src/Output/OutputInterface.php index 92a22dd8..dbbc6d2d 100644 --- a/src/Output/OutputInterface.php +++ b/src/Output/OutputInterface.php @@ -15,40 +15,38 @@ interface OutputInterface * * @param string $error */ - public function printError($error); + public function printError(string $error): void; /** * Write a string to the output. * * @param string $content */ - public function write($content); + public function write(string $content): void; /** * Write an array of strings, each on a new line. * - * @param array $lines + * @param array $lines */ - public function writeLines(array $lines); + public function writeLines(array $lines): void; /** * Write a string terminated with a newline. * * @param string $line */ - public function writeLine($line); + public function writeLine(string $line): void; /** * Write an empty line. */ - public function emptyLine(); + public function emptyLine(): void; /** * Write a line break. - * - * @return string */ - public function lineBreak(); + public function lineBreak(): void; /** * Write a title section. Should be decorated in a way which makes @@ -56,5 +54,5 @@ public function lineBreak(); * * @param string $title */ - public function writeTitle($title); + public function writeTitle(string $title): void; } diff --git a/src/Output/StdOutput.php b/src/Output/StdOutput.php index 9149954d..95260f5b 100644 --- a/src/Output/StdOutput.php +++ b/src/Output/StdOutput.php @@ -29,7 +29,7 @@ public function __construct(Color $color, Terminal $terminal) /** * @param string $error */ - public function printError($error) + public function printError(string $error): void { $length = strlen($error) + 2; echo "\n"; @@ -45,7 +45,7 @@ public function printError($error) * * @param string $title */ - public function writeTitle($title) + public function writeTitle(string $title): void { echo sprintf("\n%s\n", $this->color->__invoke($title)->underline()->bold()); } @@ -55,7 +55,7 @@ public function writeTitle($title) * * @param string $content */ - public function write($content) + public function write(string $content): void { echo $content; } @@ -63,9 +63,9 @@ public function write($content) /** * Write an array of strings, each on a new line. * - * @param array $lines + * @param array $lines */ - public function writeLines(array $lines) + public function writeLines(array $lines): void { foreach ($lines as $line) { $this->writeLine($line); @@ -77,7 +77,7 @@ public function writeLines(array $lines) * * @param string $line */ - public function writeLine($line) + public function writeLine(string $line): void { echo sprintf("%s\n", $line); } @@ -85,17 +85,15 @@ public function writeLine($line) /** * Write an empty line. */ - public function emptyLine() + public function emptyLine(): void { echo "\n"; } /** * Write a line break. - * - * @return string */ - public function lineBreak() + public function lineBreak(): void { echo $this->color->__invoke(str_repeat('─', $this->terminal->getWidth()))->yellow(); } diff --git a/src/Patch.php b/src/Patch.php index 40aa377b..b1083459 100644 --- a/src/Patch.php +++ b/src/Patch.php @@ -12,10 +12,10 @@ * An insertion is a block of code that can be inserted at the top or bottom of * the students solution. */ -class Patch +final class Patch { /** - * @var array + * @var array */ private $modifications = []; @@ -23,9 +23,9 @@ class Patch * Add a new `CodeInsertion`. `Patch` is immutable so a new instance is returned. * * @param CodeInsertion $insertion - * @return static + * @return self */ - public function withInsertion(CodeInsertion $insertion) + public function withInsertion(CodeInsertion $insertion): self { $new = clone $this; $new->modifications[] = $insertion; @@ -36,9 +36,9 @@ public function withInsertion(CodeInsertion $insertion) * Add a new transformer (`Closure`). `Patch` is immutable so a new instance is returned. * * @param Closure $closure - * @return static + * @return self */ - public function withTransformer(Closure $closure) + public function withTransformer(Closure $closure): self { $new = clone $this; $new->modifications[] = $closure; @@ -48,9 +48,9 @@ public function withTransformer(Closure $closure) /** * Retrieve all the modifications including insertions (`CodeInsertion`'s) & transformers (`Closure`'s) * - * @return array + * @return array */ - public function getModifiers() + public function getModifiers(): array { return $this->modifications; } diff --git a/src/Result/Cgi/CgiResult.php b/src/Result/Cgi/CgiResult.php index 5b24dae9..8b1e0446 100644 --- a/src/Result/Cgi/CgiResult.php +++ b/src/Result/Cgi/CgiResult.php @@ -4,11 +4,14 @@ use ArrayIterator; use IteratorAggregate; +use PhpSchool\PhpWorkshop\Result\Cgi\ResultInterface; use PhpSchool\PhpWorkshop\Result\ResultGroupInterface; /** * A result which encompasses all the results for each individual request made during * the CGI verification process. + * + * @implements IteratorAggregate */ class CgiResult implements ResultGroupInterface, IteratorAggregate { @@ -18,12 +21,12 @@ class CgiResult implements ResultGroupInterface, IteratorAggregate private $name = 'CGI Program Runner'; /** - * @var array + * @var ResultInterface[] */ private $results = []; /** - * @param array $requestResults An array of results representing each request. + * @param array $requestResults An array of results representing each request. */ public function __construct(array $requestResults = []) { @@ -37,7 +40,7 @@ public function __construct(array $requestResults = []) * * @param ResultInterface $result */ - public function add(ResultInterface $result) + public function add(ResultInterface $result): void { $this->results[] = $result; } @@ -47,7 +50,7 @@ public function add(ResultInterface $result) * * @return string */ - public function getCheckName() + public function getCheckName(): string { return $this->name; } @@ -55,7 +58,7 @@ public function getCheckName() /** * @return bool */ - public function isSuccessful() + public function isSuccessful(): bool { return count( array_filter($this->results, function ($result) { @@ -65,9 +68,9 @@ public function isSuccessful() } /** - * @return ResultInterface + * @return ResultInterface[] */ - public function getResults() + public function getResults(): array { return $this->results; } @@ -75,9 +78,9 @@ public function getResults() /** * Get an iterator in order to `foreach` the results. * - * @return ArrayIterator + * @return ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->results); } diff --git a/src/Result/Cgi/GenericFailure.php b/src/Result/Cgi/GenericFailure.php index 1f2fcd28..b8353f9e 100644 --- a/src/Result/Cgi/GenericFailure.php +++ b/src/Result/Cgi/GenericFailure.php @@ -37,11 +37,11 @@ public function __construct(RequestInterface $request, $reason = null) * * @param RequestInterface $request The request that caused the failure. * @param string|null $reason The reason (if any) of the failure. - * @return static The result. + * @return self The result. */ public static function fromRequestAndReason(RequestInterface $request, $reason) { - return new static($request, $reason); + return new self($request, $reason); } /** @@ -49,11 +49,11 @@ public static function fromRequestAndReason(RequestInterface $request, $reason) * * @param RequestInterface $request The request that caused the failure. * @param CodeExecutionException $e The exception. - * @return static The result. + * @return self The result. */ public static function fromRequestAndCodeExecutionFailure(RequestInterface $request, CodeExecutionException $e) { - return new static($request, $e->getMessage()); + return new self($request, $e->getMessage()); } /** diff --git a/src/Result/Cgi/RequestFailure.php b/src/Result/Cgi/RequestFailure.php index c3b20103..7777ad4f 100644 --- a/src/Result/Cgi/RequestFailure.php +++ b/src/Result/Cgi/RequestFailure.php @@ -26,12 +26,12 @@ class RequestFailure implements FailureInterface private $actualOutput; /** - * @var array + * @var array */ private $expectedHeaders; /** - * @var array + * @var array */ private $actualHeaders; @@ -39,21 +39,21 @@ class RequestFailure implements FailureInterface * @param RequestInterface $request The request that caused the failure. * @param string $expectedOutput * @param string $actualOutput - * @param array $expectedHeaders - * @param array $actualHeaders + * @param array $expectedHeaders + * @param array $actualHeaders */ public function __construct( RequestInterface $request, - $expectedOutput, - $actualOutput, + string $expectedOutput, + string $actualOutput, array $expectedHeaders, array $actualHeaders ) { - $this->request = $request; - $this->expectedOutput = $expectedOutput; - $this->actualOutput = $actualOutput; - $this->expectedHeaders = $expectedHeaders; - $this->actualHeaders = $actualHeaders; + $this->request = $request; + $this->expectedOutput = $expectedOutput; + $this->actualOutput = $actualOutput; + $this->expectedHeaders = $expectedHeaders; + $this->actualHeaders = $actualHeaders; } /** @@ -61,7 +61,7 @@ public function __construct( * * @return RequestInterface */ - public function getRequest() + public function getRequest(): RequestInterface { return $this->request; } @@ -71,7 +71,7 @@ public function getRequest() * * @return bool */ - public function bodyDifferent() + public function bodyDifferent(): bool { return $this->expectedOutput !== $this->actualOutput; } @@ -81,7 +81,7 @@ public function bodyDifferent() * * @return bool */ - public function headersDifferent() + public function headersDifferent(): bool { return $this->expectedHeaders !== $this->actualHeaders; } @@ -91,7 +91,7 @@ public function headersDifferent() * * @return bool */ - public function headersAndBodyDifferent() + public function headersAndBodyDifferent(): bool { return $this->bodyDifferent() && $this->headersDifferent(); } @@ -101,7 +101,7 @@ public function headersAndBodyDifferent() * * @return string */ - public function getExpectedOutput() + public function getExpectedOutput(): string { return $this->expectedOutput; } @@ -111,7 +111,7 @@ public function getExpectedOutput() * * @return string */ - public function getActualOutput() + public function getActualOutput(): string { return $this->actualOutput; } @@ -119,9 +119,9 @@ public function getActualOutput() /** * Get the array of expected headers. * - * @return array + * @return array */ - public function getExpectedHeaders() + public function getExpectedHeaders(): array { return $this->expectedHeaders; } @@ -129,9 +129,9 @@ public function getExpectedHeaders() /** * Get the array of actual headers. * - * @return array + * @return array */ - public function getActualHeaders() + public function getActualHeaders(): array { return $this->actualHeaders; } @@ -141,7 +141,7 @@ public function getActualHeaders() * * @return string */ - public function getCheckName() + public function getCheckName(): string { return 'Request Failure'; } diff --git a/src/Result/Cli/CliResult.php b/src/Result/Cli/CliResult.php index 049b1e7d..3a6ff39c 100644 --- a/src/Result/Cli/CliResult.php +++ b/src/Result/Cli/CliResult.php @@ -9,6 +9,8 @@ /** * A result which encompasses all the results for each individual execution made during * the CLI verification process. + * + * @implements IteratorAggregate */ class CliResult implements ResultGroupInterface, IteratorAggregate { @@ -18,12 +20,12 @@ class CliResult implements ResultGroupInterface, IteratorAggregate private $name = 'CLI Program Runner'; /** - * @var array + * @var ResultInterface[] */ private $results = []; /** - * @param array $requestResults An array of results representing each request. + * @param array $requestResults An array of results representing each request. */ public function __construct(array $requestResults = []) { @@ -37,7 +39,7 @@ public function __construct(array $requestResults = []) * * @param ResultInterface $result */ - public function add(ResultInterface $result) + public function add(ResultInterface $result): void { $this->results[] = $result; } @@ -47,7 +49,7 @@ public function add(ResultInterface $result) * * @return string */ - public function getCheckName() + public function getCheckName(): string { return $this->name; } @@ -55,7 +57,7 @@ public function getCheckName() /** * @return bool */ - public function isSuccessful() + public function isSuccessful(): bool { return count( array_filter($this->results, function ($result) { @@ -65,9 +67,9 @@ public function isSuccessful() } /** - * @return ResultInterface + * @return ResultInterface[] */ - public function getResults() + public function getResults(): array { return $this->results; } @@ -75,9 +77,9 @@ public function getResults() /** * Get an iterator in order to `foreach` the results. * - * @return ArrayIterator + * @return ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->results); } diff --git a/src/Result/Cli/GenericFailure.php b/src/Result/Cli/GenericFailure.php index a6224574..bdaf3ae9 100644 --- a/src/Result/Cli/GenericFailure.php +++ b/src/Result/Cli/GenericFailure.php @@ -12,7 +12,7 @@ class GenericFailure extends Failure implements FailureInterface { /** - * @var ArrayObject + * @var ArrayObject */ private $args; @@ -22,7 +22,7 @@ class GenericFailure extends Failure implements FailureInterface private static $name = 'CLI Program Runner'; /** - * @param ArrayObject $args The arguments that caused the failure. + * @param ArrayObject $args The arguments that caused the failure. * @param string|null $reason The reason (if any) of the failure. */ public function __construct(ArrayObject $args, $reason = null) @@ -34,31 +34,31 @@ public function __construct(ArrayObject $args, $reason = null) /** * Named constructor, for added code legibility. * - * @param ArrayObject $args The arguments that caused the failure. + * @param ArrayObject $args The arguments that caused the failure. * @param string|null $reason The reason (if any) of the failure. - * @return static The result. + * @return self The result. */ - public static function fromArgsAndReason(ArrayObject $args, $reason) + public static function fromArgsAndReason(ArrayObject $args, ?string $reason): self { - return new static($args, $reason); + return new self($args, $reason); } /** * Static constructor to create from a `PhpSchool\PhpWorkshop\Exception\CodeExecutionException` exception. * - * @param ArrayObject $args The arguments that caused the failure. + * @param ArrayObject $args The arguments that caused the failure. * @param CodeExecutionException $e The exception. - * @return static The result. + * @return self The result. */ - public static function fromArgsAndCodeExecutionFailure(ArrayObject $args, CodeExecutionException $e) + public static function fromArgsAndCodeExecutionFailure(ArrayObject $args, CodeExecutionException $e): self { - return new static($args, $e->getMessage()); + return new self($args, $e->getMessage()); } /** - * @return ArrayObject + * @return ArrayObject */ - public function getArgs() + public function getArgs(): ArrayObject { return $this->args; } diff --git a/src/Result/Cli/RequestFailure.php b/src/Result/Cli/RequestFailure.php index fd549e01..22db1fa4 100644 --- a/src/Result/Cli/RequestFailure.php +++ b/src/Result/Cli/RequestFailure.php @@ -11,7 +11,7 @@ class RequestFailure implements FailureInterface { /** - * @var ArrayObject + * @var ArrayObject */ private $args; @@ -26,36 +26,36 @@ class RequestFailure implements FailureInterface private $actualOutput; /** - * @param ArrayObject $args The arguments that caused the failure. + * @param ArrayObject $args The arguments that caused the failure. * @param string $expectedOutput The expected output. * @param string $actualOutput The actual output. */ - public function __construct(ArrayObject $args, $expectedOutput, $actualOutput) + public function __construct(ArrayObject $args, string $expectedOutput, string $actualOutput) { - $this->args = $args; - $this->expectedOutput = $expectedOutput; - $this->actualOutput = $actualOutput; + $this->args = $args; + $this->expectedOutput = $expectedOutput; + $this->actualOutput = $actualOutput; } /** * Named constructor, for added code legibility. * - * @param ArrayObject $args The arguments that caused the failure. + * @param ArrayObject $args The arguments that caused the failure. * @param string $expectedOutput The expected result. * @param string $actualOutput The actual output. - * @return static The result. + * @return self The result. */ - public static function fromArgsAndOutput(ArrayObject $args, $expectedOutput, $actualOutput) + public static function fromArgsAndOutput(ArrayObject $args, string $expectedOutput, string $actualOutput): self { - return new static($args, $expectedOutput, $actualOutput); + return new self($args, $expectedOutput, $actualOutput); } /** * Get the arguments that caused the failure. * - * @return ArrayObject + * @return ArrayObject */ - public function getArgs() + public function getArgs(): ArrayObject { return $this->args; } @@ -65,7 +65,7 @@ public function getArgs() * * @return string */ - public function getExpectedOutput() + public function getExpectedOutput(): string { return $this->expectedOutput; } @@ -75,7 +75,7 @@ public function getExpectedOutput() * * @return string */ - public function getActualOutput() + public function getActualOutput(): string { return $this->actualOutput; } @@ -85,7 +85,7 @@ public function getActualOutput() * * @return string */ - public function getCheckName() + public function getCheckName(): string { return 'Request Failure'; } diff --git a/src/Result/Cli/ResultInterface.php b/src/Result/Cli/ResultInterface.php index d4945878..08135cfc 100644 --- a/src/Result/Cli/ResultInterface.php +++ b/src/Result/Cli/ResultInterface.php @@ -12,7 +12,7 @@ interface ResultInterface extends \PhpSchool\PhpWorkshop\Result\ResultInterface /** * Get the arguments associated with this result. * - * @return ArrayObject + * @return ArrayObject */ - public function getArgs(); + public function getArgs(): ArrayObject; } diff --git a/src/Result/Cli/Success.php b/src/Result/Cli/Success.php index 1b995afd..9ab24e26 100644 --- a/src/Result/Cli/Success.php +++ b/src/Result/Cli/Success.php @@ -10,7 +10,7 @@ class Success implements SuccessInterface { /** - * @var ArrayObject + * @var ArrayObject */ private $args; @@ -20,7 +20,7 @@ class Success implements SuccessInterface private $name = 'CLI Program Runner'; /** - * @param ArrayObject $args The arguments for this success. + * @param ArrayObject $args The arguments for this success. */ public function __construct(ArrayObject $args) { @@ -32,7 +32,7 @@ public function __construct(ArrayObject $args) * * @return string */ - public function getCheckName() + public function getCheckName(): string { return $this->name; } @@ -40,9 +40,9 @@ public function getCheckName() /** * Get the arguments for this success. * - * @return ArrayObject + * @return ArrayObject */ - public function getArgs() + public function getArgs(): ArrayObject { return $this->args; } diff --git a/src/Result/ComparisonFailure.php b/src/Result/ComparisonFailure.php index 942bdf77..8ea5efea 100644 --- a/src/Result/ComparisonFailure.php +++ b/src/Result/ComparisonFailure.php @@ -38,11 +38,11 @@ public function __construct($name, $expectedValue, $actualValue) * @param string $name * @param string $expectedValue * @param string $actualValue - * @return static + * @return self */ public static function fromNameAndValues($name, $expectedValue, $actualValue) { - return new static($name, $expectedValue, $actualValue); + return new self($name, $expectedValue, $actualValue); } /** diff --git a/src/Result/Failure.php b/src/Result/Failure.php index b0be16c6..88a9830d 100644 --- a/src/Result/Failure.php +++ b/src/Result/Failure.php @@ -28,10 +28,10 @@ class Failure implements FailureInterface * @param string $name The name of the check that produced this result. * @param string|null $reason The reason (if any) of the failure. */ - public function __construct($name, $reason = null) + public function __construct(string $name, string $reason = null) { - $this->name = $name; - $this->reason = $reason; + $this->name = $name; + $this->reason = $reason; } /** @@ -39,11 +39,11 @@ public function __construct($name, $reason = null) * * @param string $name The name of the check that produced this result. * @param string|null $reason The reason (if any) of the failure. - * @return static The result. + * @return self The result. */ - public static function fromNameAndReason($name, $reason) + public static function fromNameAndReason(string $name, ?string $reason): self { - return new static($name, $reason); + return new self($name, $reason); } /** @@ -51,11 +51,11 @@ public static function fromNameAndReason($name, $reason) * * @param CheckInterface $check The check instance. * @param string $reason The reason (if any) of the failure. - * @return static The result. + * @return self The result. */ - public static function fromCheckAndReason(CheckInterface $check, $reason) + public static function fromCheckAndReason(CheckInterface $check, string $reason): self { - return new static($check->getName(), $reason); + return new self($check->getName(), $reason); } /** @@ -63,11 +63,11 @@ public static function fromCheckAndReason(CheckInterface $check, $reason) * * @param string $name The name of the check that produced this result. * @param CodeExecutionException $e The exception. - * @return static The result. + * @return self The result. */ - public static function fromNameAndCodeExecutionFailure($name, CodeExecutionException $e) + public static function fromNameAndCodeExecutionFailure(string $name, CodeExecutionException $e): self { - return new static($name, $e->getMessage()); + return new self($name, $e->getMessage()); } /** @@ -77,11 +77,14 @@ public static function fromNameAndCodeExecutionFailure($name, CodeExecutionExcep * @param CheckInterface $check The check that attempted to parse the solution. * @param ParseErrorException $e The parse exception. * @param string $file The absolute path to the solution. - * @return static The result. + * @return self The result. */ - public static function fromCheckAndCodeParseFailure(CheckInterface $check, ParseErrorException $e, $file) - { - return new static( + public static function fromCheckAndCodeParseFailure( + CheckInterface $check, + ParseErrorException $e, + string $file + ): self { + return new self( $check->getName(), sprintf('File: "%s" could not be parsed. Error: "%s"', $file, $e->getMessage()) ); @@ -92,7 +95,7 @@ public static function fromCheckAndCodeParseFailure(CheckInterface $check, Parse * * @return string */ - public function getCheckName() + public function getCheckName(): string { return $this->name; } @@ -102,7 +105,7 @@ public function getCheckName() * * @return string|null */ - public function getReason() + public function getReason(): ?string { return $this->reason; } diff --git a/src/Result/FunctionRequirementsFailure.php b/src/Result/FunctionRequirementsFailure.php index 66944f9a..2ed33dfd 100644 --- a/src/Result/FunctionRequirementsFailure.php +++ b/src/Result/FunctionRequirementsFailure.php @@ -13,33 +13,33 @@ class FunctionRequirementsFailure implements FailureInterface use ResultTrait; /** - * @var array + * @var array */ private $bannedFunctions; /** - * @var array + * @var array */ private $missingFunctions; /** * @param CheckInterface $check The check that produced this result. - * @param array $bannedFunctions A list of functions that were used, but were banned. - * @param array $missingFunctions A list of functions that were not used, but were required. + * @param array $bannedFunctions A list of functions that were used, but were banned. + * @param array $missingFunctions A list of functions that were not used, but were required. */ public function __construct(CheckInterface $check, array $bannedFunctions, array $missingFunctions) { - $this->check = $check; - $this->bannedFunctions = $bannedFunctions; + $this->check = $check; + $this->bannedFunctions = $bannedFunctions; $this->missingFunctions = $missingFunctions; } /** * Get the list of functions that were used, but were banned. * - * @return array + * @return array */ - public function getBannedFunctions() + public function getBannedFunctions(): array { return $this->bannedFunctions; } @@ -47,9 +47,9 @@ public function getBannedFunctions() /** * Get the list of functions that were not used, but were required. * - * @return array + * @return array */ - public function getMissingFunctions() + public function getMissingFunctions(): array { return $this->missingFunctions; } diff --git a/src/Result/Success.php b/src/Result/Success.php index 9c1bfcfb..846acb9a 100644 --- a/src/Result/Success.php +++ b/src/Result/Success.php @@ -17,7 +17,7 @@ class Success implements SuccessInterface /** * @param string $name The name of the check that produced this result. */ - public function __construct($name) + public function __construct(string $name) { $this->name = $name; } @@ -26,11 +26,11 @@ public function __construct($name) * Static constructor to create from an instance of `PhpSchool\PhpWorkshop\Check\CheckInterface`. * * @param CheckInterface $check The check instance. - * @return static The result. + * @return self The result. */ public static function fromCheck(CheckInterface $check) { - return new static($check->getName()); + return new self($check->getName()); } /** @@ -38,7 +38,7 @@ public static function fromCheck(CheckInterface $check) * * @return string */ - public function getCheckName() + public function getCheckName(): string { return $this->name; } diff --git a/src/ResultAggregator.php b/src/ResultAggregator.php index a3753bb9..dadeb624 100644 --- a/src/ResultAggregator.php +++ b/src/ResultAggregator.php @@ -12,6 +12,8 @@ /** * This class is a container to hold all the results produced * throughout the verification of a students solution. + * + * @implements IteratorAggregate */ class ResultAggregator implements IteratorAggregate { @@ -25,7 +27,7 @@ class ResultAggregator implements IteratorAggregate * * @param ResultInterface $result */ - public function add(ResultInterface $result) + public function add(ResultInterface $result): void { $this->results[] = $result; } @@ -36,7 +38,7 @@ public function add(ResultInterface $result) * * @return bool */ - public function isSuccessful() + public function isSuccessful(): bool { return count( array_filter($this->results, function ($result) { @@ -51,9 +53,9 @@ public function isSuccessful() /** * Get an iterator in order to `foreach` the results. * - * @return ArrayIterator + * @return ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->results); } diff --git a/src/ResultRenderer/Cgi/RequestFailureRenderer.php b/src/ResultRenderer/Cgi/RequestFailureRenderer.php index efa91440..9cf2d1f8 100644 --- a/src/ResultRenderer/Cgi/RequestFailureRenderer.php +++ b/src/ResultRenderer/Cgi/RequestFailureRenderer.php @@ -30,7 +30,7 @@ public function __construct(RequestFailure $result) * @param ResultsRenderer $renderer * @return string */ - public function render(ResultsRenderer $renderer) + public function render(ResultsRenderer $renderer): string { $output = ''; if ($this->result->headersDifferent()) { @@ -61,12 +61,12 @@ public function render(ResultsRenderer $renderer) } /** - * @param array $headers + * @param array $headers * @param ResultsRenderer $renderer * @param bool $actual * @return string */ - private function headers(array $headers, ResultsRenderer $renderer, $actual = true) + private function headers(array $headers, ResultsRenderer $renderer, $actual = true): string { $indent = false; $output = ''; diff --git a/src/ResultRenderer/CgiResultRenderer.php b/src/ResultRenderer/CgiResultRenderer.php index 020c2f0f..cf6b6266 100644 --- a/src/ResultRenderer/CgiResultRenderer.php +++ b/src/ResultRenderer/CgiResultRenderer.php @@ -38,7 +38,7 @@ public function __construct(CgiResult $result, RequestRenderer $requestRenderer) * @param ResultsRenderer $renderer * @return string */ - public function render(ResultsRenderer $renderer) + public function render(ResultsRenderer $renderer): string { $results = array_filter($this->result->getResults(), function (ResultInterface $result) { return $result instanceof FailureInterface; diff --git a/src/ResultRenderer/CliResultRenderer.php b/src/ResultRenderer/CliResultRenderer.php index 643d2454..23a08110 100644 --- a/src/ResultRenderer/CliResultRenderer.php +++ b/src/ResultRenderer/CliResultRenderer.php @@ -30,7 +30,7 @@ public function __construct(CliResult $result) * @param ResultsRenderer $renderer * @return string */ - public function render(ResultsRenderer $renderer) + public function render(ResultsRenderer $renderer): string { $results = array_filter($this->result->getResults(), function (ResultInterface $result) { return $result instanceof FailureInterface; diff --git a/src/ResultRenderer/ComparisonFailureRenderer.php b/src/ResultRenderer/ComparisonFailureRenderer.php index 8c14c16f..752f097f 100644 --- a/src/ResultRenderer/ComparisonFailureRenderer.php +++ b/src/ResultRenderer/ComparisonFailureRenderer.php @@ -11,7 +11,7 @@ class ComparisonFailureRenderer implements ResultRendererInterface { /** - * @var RequestFailure + * @var ComparisonFailure */ private $result; diff --git a/src/ResultRenderer/FailureRenderer.php b/src/ResultRenderer/FailureRenderer.php index 403e1913..e141a6e7 100644 --- a/src/ResultRenderer/FailureRenderer.php +++ b/src/ResultRenderer/FailureRenderer.php @@ -28,8 +28,8 @@ public function __construct(Failure $result) * @param ResultsRenderer $renderer * @return string */ - public function render(ResultsRenderer $renderer) + public function render(ResultsRenderer $renderer): string { - return $renderer->center($this->result->getReason()) . "\n"; + return $renderer->center((string) $this->result->getReason()) . "\n"; } } diff --git a/src/ResultRenderer/ResultsRenderer.php b/src/ResultRenderer/ResultsRenderer.php index 8a0feb4e..827bccfe 100644 --- a/src/ResultRenderer/ResultsRenderer.php +++ b/src/ResultRenderer/ResultsRenderer.php @@ -5,6 +5,7 @@ use Colors\Color; use Kadet\Highlighter\Formatter\CliFormatter; use Kadet\Highlighter\KeyLighter; +use PhpSchool\PhpWorkshop\Result\FailureInterface; use PhpSchool\Terminal\Terminal; use PhpSchool\CliMenu\Util\StringUtil; use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution; @@ -91,7 +92,7 @@ public function render( ExerciseInterface $exercise, UserState $userState, OutputInterface $output - ) { + ): void { $successes = []; $failures = []; foreach ($results as $result) { @@ -104,7 +105,7 @@ public function render( $failures[] = [$result, sprintf(' ✗ Check: %s', $result->getCheckName())]; } } - + /** @var array $failures */ $output->emptyLine(); $output->writeLine($this->center($this->style('*** RESULTS ***', ['magenta', 'bold']))); $output->emptyLine(); @@ -122,13 +123,14 @@ public function render( } if ($results->isSuccessful()) { - return $this->renderSuccessInformation($exercise, $userState, $output); + $this->renderSuccessInformation($exercise, $userState, $output); + return; } $this->renderErrorInformation($failures, $longest, $exercise, $output); } /** - * @param array $failures + * @param array $failures * @param int $padLength * @param ExerciseInterface $exercise * @param OutputInterface $output @@ -138,8 +140,8 @@ private function renderErrorInformation( $padLength, ExerciseInterface $exercise, OutputInterface $output - ) { - foreach ($failures as list ($failure, $message)) { + ): void { + foreach ($failures as [$failure, $message]) { $output->writeLine($this->center($this->style(str_repeat(' ', $padLength), ['bg_red']))); $output->writeLine($this->center($this->style(\mb_str_pad($message, $padLength), ['bg_red']))); $output->writeLine($this->center($this->style(str_repeat(' ', $padLength), ['bg_red']))); @@ -170,7 +172,7 @@ private function renderSuccessInformation( ExerciseInterface $exercise, UserState $userState, OutputInterface $output - ) { + ): void { $output->lineBreak(); $output->emptyLine(); $output->emptyLine(); @@ -213,7 +215,7 @@ private function renderSuccessInformation( * @param string $string * @return string string */ - public function center($string) + public function center(string $string): string { $stringHalfLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($string)) / 2; $widthHalfLength = ceil($this->terminal->getWidth() / 2); @@ -223,15 +225,15 @@ public function center($string) $start = 0; } - return str_repeat(' ', $start) . $string; + return str_repeat(' ', (int) $start) . $string; } /** * @param OutputInterface $output - * @param $string - * @param array $style + * @param string $string + * @param array $style */ - private function fullWidthBlock(OutputInterface $output, $string, array $style) + private function fullWidthBlock(OutputInterface $output, string $string, array $style): void { $stringLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($string)); $stringHalfLength = $stringLength / 2; @@ -243,9 +245,9 @@ private function fullWidthBlock(OutputInterface $output, $string, array $style) $this->style( sprintf( '%s%s%s', - str_repeat(' ', $start), + str_repeat(' ', (int) $start), $string, - str_repeat(' ', $this->terminal->getWidth() - $stringLength - $start) + str_repeat(' ', (int) ($this->terminal->getWidth() - $stringLength - $start)) ), $style ) @@ -258,12 +260,12 @@ private function fullWidthBlock(OutputInterface $output, $string, array $style) * Can be any of: black, red, green, yellow, blue, magenta, cyan, white, bold, italic, underline. * * @param string $string - * @param array|string $colourOrStyle A single style as a string or multiple styles as an array. + * @param array|string $colourOrStyle A single style as a string or multiple styles as an array. * * @return string * */ - public function style($string, $colourOrStyle) + public function style(string $string, $colourOrStyle): string { if (is_array($colourOrStyle)) { $this->color->__invoke($string); @@ -283,7 +285,7 @@ public function style($string, $colourOrStyle) * @param ResultInterface $result The result. * @return string The string representation of the result. */ - public function renderResult(ResultInterface $result) + public function renderResult(ResultInterface $result): string { return $this->resultRendererFactory->create($result)->render($this); } @@ -293,7 +295,7 @@ public function renderResult(ResultInterface $result) * * @return string */ - public function lineBreak() + public function lineBreak(): string { return $this->style(str_repeat('─', $this->terminal->getWidth()), 'yellow'); } diff --git a/src/Solution/DirectorySolution.php b/src/Solution/DirectorySolution.php index 0d8e93bc..bdfdb052 100644 --- a/src/Solution/DirectorySolution.php +++ b/src/Solution/DirectorySolution.php @@ -12,14 +12,13 @@ */ class DirectorySolution implements SolutionInterface { - /** * @var string */ private $entryPoint; /** - * @var SolutionFile[] + * @var array */ private $files = []; @@ -35,18 +34,18 @@ class DirectorySolution implements SolutionInterface * * @param string $directory The directory to search for files. * @param string $entryPoint The relative path from the directory of the entry point file. - * @param array $exclusions An array of file names to exclude from the folder. + * @param array $exclusions An array of file names to exclude from the folder. * @throws InvalidArgumentException If the entry point does not exist in the folder. */ - public function __construct($directory, $entryPoint, array $exclusions = []) + public function __construct(string $directory, string $entryPoint, array $exclusions = []) { - $directory = realpath(rtrim($directory, '/')); + $directory = (string) realpath(rtrim($directory, '/')); $entryPoint = ltrim($entryPoint, '/'); $dir = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); $iter = new RecursiveIteratorIterator( new RecursiveCallbackFilterIterator($dir, function (\SplFileInfo $current) use ($exclusions) { - return !in_array($current->getBasename(), $exclusions); + return !in_array($current->getBasename(), $exclusions, true); }), RecursiveIteratorIterator::SELF_FIRST ); @@ -77,13 +76,13 @@ public function __construct($directory, $entryPoint, array $exclusions = []) * Static constructor to build an instance from a directory. * * @param string $directory The directory to search for files. - * @param array $exclusions An array of file names to exclude from the folder. + * @param array $exclusions An array of file names to exclude from the folder. * @param string $entryPoint The relative path from the directory of the entry point file. - * @return static + * @return self */ - public static function fromDirectory($directory, array $exclusions = [], $entryPoint = 'solution.php') + public static function fromDirectory(string $directory, array $exclusions = [], $entryPoint = 'solution.php'): self { - return new static($directory, $entryPoint, array_merge($exclusions, ['composer.lock', 'vendor'])); + return new self($directory, $entryPoint, array_merge($exclusions, ['composer.lock', 'vendor'])); } /** @@ -92,7 +91,7 @@ public static function fromDirectory($directory, array $exclusions = [], $entryP * * @return string */ - public function getEntryPoint() + public function getEntryPoint(): string { return $this->entryPoint; } @@ -100,9 +99,9 @@ public function getEntryPoint() /** * Get all the files which are contained with the solution. * - * @return SolutionFile[] + * @return array */ - public function getFiles() + public function getFiles(): array { return $this->files; } @@ -112,7 +111,7 @@ public function getFiles() * * @return string */ - public function getBaseDirectory() + public function getBaseDirectory(): string { return $this->baseDirectory; } @@ -122,7 +121,7 @@ public function getBaseDirectory() * * @return bool */ - public function hasComposerFile() + public function hasComposerFile(): bool { return file_exists(sprintf('%s/composer.lock', $this->baseDirectory)); } diff --git a/src/Solution/SingleFileSolution.php b/src/Solution/SingleFileSolution.php index 9c579c15..7611c983 100644 --- a/src/Solution/SingleFileSolution.php +++ b/src/Solution/SingleFileSolution.php @@ -9,7 +9,6 @@ */ class SingleFileSolution implements SolutionInterface { - /** * @var SolutionFile */ @@ -19,21 +18,21 @@ class SingleFileSolution implements SolutionInterface * @param string $file The absolute path of the reference solution. * @throws InvalidArgumentException If the file does not exist. */ - public function __construct($file) + public function __construct(string $file) { - $this->file = SolutionFile::fromFile(realpath($file)); + $this->file = SolutionFile::fromFile((string) realpath($file)); } /** * Static constructor to build an instance from an absolute file path. * * @param string $file The absolute path of the reference solution. - * @return static + * @return self * @throws InvalidArgumentException If the file does not exist. */ - public static function fromFile($file) + public static function fromFile(string $file): self { - return new static($file); + return new self($file); } /** @@ -42,7 +41,7 @@ public static function fromFile($file) * * @return string */ - public function getEntryPoint() + public function getEntryPoint(): string { return $this->file->__toString(); } @@ -50,7 +49,7 @@ public function getEntryPoint() /** * Get all the files which are contained with the solution. * - * @return SolutionFile[] + * @return array */ public function getFiles(): array { @@ -62,7 +61,7 @@ public function getFiles(): array * * @return string */ - public function getBaseDirectory() + public function getBaseDirectory(): string { return $this->file->getBaseDirectory(); } @@ -73,7 +72,7 @@ public function getBaseDirectory() * * @return bool */ - public function hasComposerFile() + public function hasComposerFile(): bool { return false; } diff --git a/src/Solution/SolutionFile.php b/src/Solution/SolutionFile.php index b052ab38..47f5d988 100644 --- a/src/Solution/SolutionFile.php +++ b/src/Solution/SolutionFile.php @@ -24,7 +24,7 @@ class SolutionFile * @param string $baseDirectory The base directory of the solution. * @throws InvalidArgumentException If the file does not exist. */ - public function __construct($relativePath, $baseDirectory) + public function __construct(string $relativePath, string $baseDirectory) { $this->relativePath = trim($relativePath, '/'); $this->baseDirectory = rtrim($baseDirectory, '/'); @@ -39,11 +39,11 @@ public function __construct($relativePath, $baseDirectory) * Will assume the base directory should be the immediate parent of the file. * * @param string $file - * @return static + * @return self */ - public static function fromFile($file) + public static function fromFile(string $file): self { - return new static(basename($file), dirname($file)); + return new self(basename($file), dirname($file)); } /** @@ -51,7 +51,7 @@ public static function fromFile($file) * * @return string */ - private function getAbsolutePath() + private function getAbsolutePath(): string { return sprintf('%s/%s', $this->baseDirectory, $this->relativePath); } @@ -61,7 +61,7 @@ private function getAbsolutePath() * * @return string */ - public function getRelativePath() + public function getRelativePath(): string { return $this->relativePath; } @@ -71,7 +71,7 @@ public function getRelativePath() * * @return string */ - public function getBaseDirectory() + public function getBaseDirectory(): string { return $this->baseDirectory; } @@ -81,9 +81,9 @@ public function getBaseDirectory() * * @return string */ - public function getContents() + public function getContents(): string { - return file_get_contents($this->getAbsolutePath()); + return (string) file_get_contents($this->getAbsolutePath()); } /** @@ -91,7 +91,7 @@ public function getContents() * * @return string */ - public function getExtension() + public function getExtension(): string { return pathinfo($this->getRelativePath(), PATHINFO_EXTENSION); } @@ -101,7 +101,7 @@ public function getExtension() * * @return string */ - public function __toString() + public function __toString(): string { return $this->getAbsolutePath(); } diff --git a/src/Solution/SolutionInterface.php b/src/Solution/SolutionInterface.php index 7301e1ef..5df660c1 100644 --- a/src/Solution/SolutionInterface.php +++ b/src/Solution/SolutionInterface.php @@ -13,26 +13,26 @@ interface SolutionInterface * * @return string */ - public function getEntryPoint(); + public function getEntryPoint(): string; /** * Get all the files which are contained with the solution. * - * @return SolutionFile[] + * @return array */ - public function getFiles(); + public function getFiles(): array; /** * Get the absolute path to the directory containing the solution. * * @return string */ - public function getBaseDirectory(); + public function getBaseDirectory(): string; /** * Whether or not the solution has a `composer.json` & `composer.lock` file. * * @return bool */ - public function hasComposerFile(); + public function hasComposerFile(): bool; } diff --git a/src/UserState.php b/src/UserState.php index ff4543ab..33b1b0d2 100644 --- a/src/UserState.php +++ b/src/UserState.php @@ -2,20 +2,21 @@ namespace PhpSchool\PhpWorkshop; +use PhpSchool\PhpWorkshop\Exception\ExerciseNotAssignedException; + /** * This class represents the current state of the user. Which exercises she/he has completed and * which is the current exercise being attempted. */ class UserState { - /** - * @var string + * @var string|null */ private $currentExercise; /** - * @var array + * @var array */ private $completedExercises; @@ -23,10 +24,10 @@ class UserState * Take an array of completed exercises (the exercise names) and a string containing the current * exercise. * - * @param array $completedExercises An array of exercise names. - * @param string $currentExercise Can be null in-case the student did not start an exercise yet. + * @param array $completedExercises An array of exercise names. + * @param string|null $currentExercise Can be null in-case the student did not start an exercise yet. */ - public function __construct(array $completedExercises = [], $currentExercise = null) + public function __construct(array $completedExercises = [], string $currentExercise = null) { $this->currentExercise = $currentExercise; $this->completedExercises = $completedExercises; @@ -37,9 +38,9 @@ public function __construct(array $completedExercises = [], $currentExercise = n * * @param string $exercise */ - public function addCompletedExercise($exercise) + public function addCompletedExercise(string $exercise): void { - if (!in_array($exercise, $this->completedExercises)) { + if (!in_array($exercise, $this->completedExercises, true)) { $this->completedExercises[] = $exercise; } } @@ -49,7 +50,7 @@ public function addCompletedExercise($exercise) * * @param string $exercise */ - public function setCurrentExercise($exercise) + public function setCurrentExercise(string $exercise): void { $this->currentExercise = $exercise; } @@ -59,17 +60,21 @@ public function setCurrentExercise($exercise) * * @return string */ - public function getCurrentExercise() + public function getCurrentExercise(): string { + if (null === $this->currentExercise) { + throw ExerciseNotAssignedException::notAssigned(); + } + return $this->currentExercise; } /** * Get an array of the completed exercises (the exercise names). * - * @return array + * @return array */ - public function getCompletedExercises() + public function getCompletedExercises(): array { return $this->completedExercises; } @@ -79,7 +84,7 @@ public function getCompletedExercises() * * @return bool */ - public function isAssignedExercise() + public function isAssignedExercise(): bool { return null !== $this->currentExercise; } @@ -90,8 +95,8 @@ public function isAssignedExercise() * @param string $exercise * @return bool */ - public function completedExercise($exercise) + public function completedExercise(string $exercise): bool { - return in_array($exercise, $this->completedExercises); + return in_array($exercise, $this->completedExercises, true); } } diff --git a/src/UserStateSerializer.php b/src/UserStateSerializer.php index afcd5524..e2a5a7a0 100644 --- a/src/UserStateSerializer.php +++ b/src/UserStateSerializer.php @@ -52,9 +52,9 @@ public function __construct($saveFileDirectory, $workshopName, ExerciseRepositor * Save the students state for this workshop to disk. * * @param UserState $state - * @return int + * @return void */ - public function serialize(UserState $state) + public function serialize(UserState $state): void { $saveFile = sprintf('%s/%s', $this->path, static::SAVE_FILE); @@ -64,10 +64,10 @@ public function serialize(UserState $state) $data[$this->workshopName] = [ 'completed_exercises' => $state->getCompletedExercises(), - 'current_exercise' => $state->getCurrentExercise(), + 'current_exercise' => $state->isAssignedExercise() ? $state->getCurrentExercise() : null, ]; - return file_put_contents($saveFile, json_encode($data)); + file_put_contents($saveFile, json_encode($data)); } /** @@ -75,7 +75,7 @@ public function serialize(UserState $state) * * @return UserState */ - public function deSerialize() + public function deSerialize(): UserState { $legacySaveFile = sprintf('%s/%s', $this->path, static::LEGACY_SAVE_FILE); if (file_exists($legacySaveFile)) { @@ -136,7 +136,7 @@ public function deSerialize() * @param string $legacySaveFile * @return null|UserState */ - private function migrateData($legacySaveFile) + private function migrateData(string $legacySaveFile): ?UserState { $data = $this->readJson($legacySaveFile); @@ -171,15 +171,15 @@ private function migrateData($legacySaveFile) /** * @param string $filePath - * @return array|null + * @return array|null */ - private function readJson($filePath) + private function readJson($filePath): ?array { if (!file_exists($filePath)) { return null; } - $data = file_get_contents($filePath); + $data = (string) file_get_contents($filePath); if (trim($data) === "") { return null; @@ -197,7 +197,7 @@ private function readJson($filePath) /** * Remove the file */ - private function wipeFile() + private function wipeFile(): void { @unlink($this->path); } diff --git a/src/Utils/ArrayObject.php b/src/Utils/ArrayObject.php index b22990e9..62424202 100644 --- a/src/Utils/ArrayObject.php +++ b/src/Utils/ArrayObject.php @@ -7,22 +7,26 @@ use IteratorAggregate; /** + * * Utility collection class. + * + * @template T + * @implements IteratorAggregate */ class ArrayObject implements IteratorAggregate, Countable { /** - * @var array + * @var array */ private $array; /** * Accepts an array of items. * - * @param array $array + * @param array $array */ - public function __construct(array $array = []) + final public function __construct(array $array = []) { $this->array = $array; } @@ -34,11 +38,20 @@ public function __construct(array $array = []) * @param callable $callback * @return static */ - public function map(callable $callback) + public function map(callable $callback): self { return new static(array_map($callback, $this->array)); } + /** + * @param callable $callback + * @return static + */ + public function filter(callable $callback): self + { + return new static(array_filter($this->array, $callback)); + } + /** * Run a callable over each item in the array and flatten the results by one level returning a new instance of * `ArrayObject` with the flattened items. @@ -46,7 +59,7 @@ public function map(callable $callback) * @param callable $callback * @return static */ - public function flatMap(callable $callback) + public function flatMap(callable $callback): self { return $this->map($callback)->collapse(); } @@ -57,7 +70,7 @@ public function flatMap(callable $callback) * * @return static */ - public function collapse() + public function collapse(): self { $results = []; @@ -75,8 +88,8 @@ public function collapse() /** * Reduce the items to a single value. * - * @param callable $callback - * @param mixed $initial + * @param callable $callback + * @param mixed $initial * @return mixed */ public function reduce(callable $callback, $initial = null) @@ -87,7 +100,7 @@ public function reduce(callable $callback, $initial = null) /** * @return static */ - public function keys() + public function keys(): self { return new static(array_keys($this->array)); } @@ -98,7 +111,7 @@ public function keys() * @param string $glue * @return string */ - public function implode($glue) + public function implode(string $glue): string { return implode($glue, $this->array); } @@ -106,10 +119,10 @@ public function implode($glue) /** * Add a new item on to the beginning of the collection. A new instance is returned. * - * @param mixed $value + * @param T $value * @return static */ - public function prepend($value) + public function prepend($value): self { return new static(array_merge([$value], $this->array)); } @@ -117,10 +130,10 @@ public function prepend($value) /** * Add a new item to the end of the collection. A new instance is returned. * - * @param mixed $value + * @param T $value * @return static */ - public function append($value) + public function append($value): self { return new static(array_merge($this->array, [$value])); } @@ -128,11 +141,11 @@ public function append($value) /** * Get an item at the given key. * - * @param mixed $key + * @param string $key * @param mixed $default - * @return mixed + * @return T|mixed */ - public function get($key, $default = null) + public function get(string $key, $default = null) { if (isset($this->array[$key])) { return $this->array[$key]; @@ -144,11 +157,11 @@ public function get($key, $default = null) /** * Set the item at a given offset and return a new instance. * - * @param mixed $key - * @param mixed $value + * @param string $key + * @param T $value * @return static */ - public function set($key, $value) + public function set(string $key, $value): self { $items = $this->array; $items[$key] = $value; @@ -158,9 +171,9 @@ public function set($key, $value) /** * Return an iterator containing all the items. Allows to `foreach` over. * - * @return ArrayIterator + * @return ArrayIterator */ - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->array); } @@ -168,9 +181,9 @@ public function getIterator() /** * Get all the items in the array. * - * @return array + * @return array */ - public function getArrayCopy() + public function getArrayCopy(): array { return $this->array; } @@ -180,7 +193,7 @@ public function getArrayCopy() * * @return int */ - public function count() + public function count(): int { return count($this->array); } @@ -188,7 +201,7 @@ public function count() /** * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return $this->array === []; } diff --git a/src/Utils/Collection.php b/src/Utils/Collection.php index a4dc642e..2bb60621 100644 --- a/src/Utils/Collection.php +++ b/src/Utils/Collection.php @@ -4,6 +4,9 @@ /** * ArrayObject is not a good name... + * + * @template T + * @extends ArrayObject */ class Collection extends ArrayObject { diff --git a/src/WorkshopType.php b/src/WorkshopType.php index b6b138b4..6cf6c93e 100644 --- a/src/WorkshopType.php +++ b/src/WorkshopType.php @@ -7,6 +7,8 @@ /** * @method static WorkshopType STANDARD() * @method static WorkshopType TUTORIAL() + * + * @extends Enum */ class WorkshopType extends Enum { @@ -16,7 +18,7 @@ class WorkshopType extends Enum /** * @return bool */ - public function isTutorialMode() + public function isTutorialMode(): bool { return $this->getValue() === static::TUTORIAL; } diff --git a/src/functions.php b/src/functions.php index 2496b3c4..d792efab 100644 --- a/src/functions.php +++ b/src/functions.php @@ -9,7 +9,7 @@ * @param int $padType * @return string */ - function mb_str_pad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + function mb_str_pad(string $input, int $padLength, string $padString = ' ', int $padType = STR_PAD_RIGHT): string { $diff = strlen($input) - mb_strlen($input); return str_pad($input, $padLength + $diff, $padString, $padType); @@ -22,9 +22,9 @@ function mb_str_pad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIG * @param string $string * @return string */ - function camel_case_to_kebab_case($string) + function camel_case_to_kebab_case(string $string): string { - return preg_replace_callback('/[A-Z]/', function ($matches) { + return (string) preg_replace_callback('/[A-Z]/', function ($matches) { return '-' . strtolower($matches[0]); }, $string); } diff --git a/test/CodeInsertionTest.php b/test/CodeInsertionTest.php index cbcd49da..b385ca71 100644 --- a/test/CodeInsertionTest.php +++ b/test/CodeInsertionTest.php @@ -14,12 +14,6 @@ public function testInvalidType(): void new CodeInsertion('notatype', ''); } - public function testInvalidCode(): void - { - $this->expectException(InvalidArgumentException::class); - new CodeInsertion(CodeInsertion::TYPE_BEFORE, new \stdClass()); - } - public function testGetters(): void { $mod = new CodeInsertion(CodeInsertion::TYPE_BEFORE, 'createMock(ResultsRenderer::class); $results = new ResultAggregator(); - $results->add(new Success($this->check)); + $results->add(Success::fromCheck($this->check)); $dispatcher = $this->createMock(ExerciseDispatcher::class); @@ -106,7 +106,7 @@ public function testVerifyDoesNotAddCompletedExerciseAndReturnsCorrectCodeOnFail $renderer = $this->createMock(ResultsRenderer::class); $results = new ResultAggregator(); - $results->add(new Failure($this->check, 'cba')); + $results->add(Failure::fromCheckAndReason($this->check, 'cba')); $dispatcher = $this->createMock(ExerciseDispatcher::class); diff --git a/test/Factory/ResultRendererFactoryTest.php b/test/Factory/ResultRendererFactoryTest.php index 6088f325..110dad9a 100644 --- a/test/Factory/ResultRendererFactoryTest.php +++ b/test/Factory/ResultRendererFactoryTest.php @@ -33,28 +33,6 @@ public function testRegisterRendererRequiresResultRendererInterface(): void $factory->registerRenderer($resultClass, $rendererClass); } - public function testRegisterRendererRequiresResultClassToBeString(): void - { - $this->expectException(InvalidArgumentException::class); - - $resultClass = $this->createMock(ResultInterface::class); - $rendererClass = get_class($this->createMock(ResultRendererInterface::class)); - $factory = new ResultRendererFactory(); - - $factory->registerRenderer($resultClass, $rendererClass); - } - - public function testRegisterRendererRequiresRendererClassToBeString(): void - { - $this->expectException(InvalidArgumentException::class); - - $resultClass = get_class($this->createMock(ResultInterface::class)); - $rendererClass = $this->createMock(ResultRendererInterface::class); - $factory = new ResultRendererFactory(); - - $factory->registerRenderer($resultClass, $rendererClass); - } - public function testCreateRequiresMappingToClassName(): void { $this->expectException(RuntimeException::class); diff --git a/test/ResultAggregatorTest.php b/test/ResultAggregatorTest.php index e34da8d4..77eceff6 100644 --- a/test/ResultAggregatorTest.php +++ b/test/ResultAggregatorTest.php @@ -32,10 +32,10 @@ public function testIsSuccessful(): void $resultAggregator = new ResultAggregator(); $this->assertTrue($resultAggregator->isSuccessful()); - $resultAggregator->add(new Success($this->check)); + $resultAggregator->add(Success::fromCheck($this->check)); $this->assertTrue($resultAggregator->isSuccessful()); - $resultAggregator->add(new Failure($this->check, 'nope')); + $resultAggregator->add(Failure::fromCheckAndReason($this->check, 'nope')); $this->assertFalse($resultAggregator->isSuccessful()); } @@ -58,8 +58,8 @@ public function testIsSuccessfulWithResultGroups(): void public function testIterator(): void { $results = [ - new Success($this->check), - new Failure($this->check, 'nope') + new Success('Some Check'), + new Failure('Some Check', 'nope') ]; $resultAggregator = new ResultAggregator(); diff --git a/test/ResultRenderer/FailureRendererTest.php b/test/ResultRenderer/FailureRendererTest.php index 6b1959af..9884f07f 100644 --- a/test/ResultRenderer/FailureRendererTest.php +++ b/test/ResultRenderer/FailureRendererTest.php @@ -10,7 +10,7 @@ class FailureRendererTest extends AbstractResultRendererTest { public function testRender(): void { - $failure = new Failure($this->createMock(CheckInterface::class), 'Something went wrong'); + $failure = new Failure('My check', 'Something went wrong'); $renderer = new FailureRenderer($failure); $this->assertEquals(" Something went wrong\n", $renderer->render($this->getRenderer())); } diff --git a/test/UserStateSerializerTest.php b/test/UserStateSerializerTest.php index c2980a41..75a6e50c 100644 --- a/test/UserStateSerializerTest.php +++ b/test/UserStateSerializerTest.php @@ -66,8 +66,7 @@ public function testSerializeEmptySate(): void ] ]); - $res = $serializer->serialize($state); - $this->assertTrue($res > 0); + $serializer->serialize($state); $this->assertSame($expected, file_get_contents($this->tmpFile)); } @@ -86,8 +85,8 @@ public function testSerialize(): void ] ]); - $res = $serializer->serialize($state); - $this->assertTrue($res > 0); + $serializer->serialize($state); + $this->assertSame($expected, file_get_contents($this->tmpFile)); } @@ -96,7 +95,7 @@ public function testDeserializeNonExistingFile(): void mkdir($this->tmpDir, 0777, true); $serializer = new UserStateSerializer($this->tmpDir, $this->workshopName, $this->exerciseRepository); $state = $serializer->deSerialize(); - $this->assertNull($state->getCurrentExercise()); + $this->assertFalse($state->isAssignedExercise()); $this->assertEmpty($state->getCompletedExercises()); } @@ -106,7 +105,7 @@ public function testDeserializeEmptyFile(): void file_put_contents($this->tmpFile, ''); $serializer = new UserStateSerializer($this->tmpDir, $this->workshopName, $this->exerciseRepository); $state = $serializer->deSerialize(); - $this->assertNull($state->getCurrentExercise()); + $this->assertFalse($state->isAssignedExercise()); $this->assertEmpty($state->getCompletedExercises()); } @@ -116,7 +115,7 @@ public function testDeserializeNonValidJson(): void file_put_contents($this->tmpFile, 'yayayayayanotjson'); $serializer = new UserStateSerializer($this->tmpDir, $this->workshopName, $this->exerciseRepository); $state = $serializer->deSerialize(); - $this->assertNull($state->getCurrentExercise()); + $this->assertFalse($state->isAssignedExercise()); $this->assertEmpty($state->getCompletedExercises()); } @@ -131,7 +130,10 @@ public function testDeserialize(array $data, array $expected): void $state = $serializer->deSerialize(); $this->assertEquals($expected['completed_exercises'], $state->getCompletedExercises()); - $this->assertEquals($expected['current_exercise'], $state->getCurrentExercise()); + $this->assertEquals( + $expected['current_exercise'], + $state->isAssignedExercise() ? $state->getCurrentExercise() : null + ); if (file_exists($this->tmpFile)) { unlink($this->tmpFile); @@ -256,7 +258,7 @@ public function testOldDataWillNotBeMigratedWhenNotInCorrectWorkshop(): void $state = $serializer->deSerialize(); $this->assertEquals([], $state->getCompletedExercises()); - $this->assertEquals(null, $state->getCurrentExercise()); + $this->assertFalse($state->isAssignedExercise()); $this->assertFileExists($oldSave); $this->assertEquals($oldData, json_decode(file_get_contents($oldSave), true)); diff --git a/test/UserStateTest.php b/test/UserStateTest.php index 60db1b10..166dc629 100644 --- a/test/UserStateTest.php +++ b/test/UserStateTest.php @@ -2,6 +2,7 @@ namespace PhpSchool\PhpWorkshopTest; +use PhpSchool\PhpWorkshop\Exception\ExerciseNotAssignedException; use PHPUnit\Framework\TestCase; use PhpSchool\PhpWorkshop\UserState; @@ -12,7 +13,16 @@ public function testWithNoCurrentExercisesOrCompleted(): void $state = new UserState(); $this->assertFalse($state->isAssignedExercise()); $this->assertEmpty($state->getCompletedExercises()); - $this->assertNull($state->getCurrentExercise()); + } + + public function testGetCurrentExerciseThrowsExceptionIfNonAssigned(): void + { + $this->expectException(ExerciseNotAssignedException::class); + $this->expectExceptionMessage('Student has no exercise assigned'); + + $state = new UserState(); + $this->assertFalse($state->isAssignedExercise()); + $state->getCurrentExercise(); } public function testWithCurrentExerciseButNoCompleted(): void @@ -36,7 +46,6 @@ public function testWithCompletedExerciseButNoCurrent(): void $state = new UserState(['exercise1']); $this->assertFalse($state->isAssignedExercise()); $this->assertSame(['exercise1'], $state->getCompletedExercises()); - $this->assertNull($state->getCurrentExercise()); } public function testAddCompletedExercise(): void @@ -56,7 +65,6 @@ public function testSetCompletedExercise(): void { $state = new UserState(['exercise1']); $this->assertFalse($state->isAssignedExercise()); - $this->assertNull($state->getCurrentExercise()); $this->assertSame(['exercise1'], $state->getCompletedExercises()); $state->setCurrentExercise('exercise2');