Skip to content

Commit a626ff5

Browse files
authored
Merge pull request #27 from php-school/throw-an-expression
Throw an expression
2 parents f090d58 + 26de538 commit a626ff5

File tree

16 files changed

+280
-11
lines changed

16 files changed

+280
-11
lines changed

app/bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
2929
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
3030
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
31+
use PhpSchool\PHP8Appreciate\Exercise\ThrowAnExpression;
3132
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
3233
use PhpSchool\PhpWorkshop\Application;
3334

@@ -43,6 +44,7 @@
4344
$app->addExercise(ASafeSpaceForNulls::class);
4445
$app->addExercise(AllMixedUp::class);
4546
$app->addExercise(TheReturnOfStatic::class);
47+
$app->addExercise(ThrowAnExpression::class);
4648

4749
$art = <<<ART
4850
_ __ _

app/config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
1010
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
1111
use PhpSchool\PHP8Appreciate\Exercise\TheReturnOfStatic;
12+
use PhpSchool\PHP8Appreciate\Exercise\ThrowAnExpression;
1213
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
1314
use Psr\Container\ContainerInterface;
1415

@@ -48,5 +49,8 @@
4849
},
4950
TheReturnOfStatic::class => function (ContainerInterface $c) {
5051
return new TheReturnOfStatic($c->get(PhpParser\Parser::class));
52+
},
53+
ThrowAnExpression::class => function (ContainerInterface $c) {
54+
return new ThrowAnExpression($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
5155
}
5256
];

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
],
2222
"require": {
2323
"php": "^8.0",
24-
"php-school/php-workshop": "dev-master"
24+
"php-school/php-workshop": "dev-wrap-in-try-catch-patch"
2525
},
2626
"require-dev": {
2727
"phpunit/phpunit": "^9",

composer.lock

Lines changed: 7 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
if (str_starts_with($_SERVER['REQUEST_URI'], '/forbidden')) {
4+
throw new InvalidArgumentException('Access denied!');
5+
} else {
6+
echo 'Welcome!';
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
You have been given a piece of code (look for `throw-an-expression.php` in your working directory) which is checking the requested URL and throwing an exception when a secret area of the website is accessed.
2+
3+
If the request is allowed, `Welcome!` is printed out.
4+
5+
6+
Traditionally, pre PHP 8, an exception throw has been a statement. There are certain places where statements cannot be used, and only expressions can be used. For example, in ternaries and short closures, only expressions can be used.
7+
8+
Now with PHP 8, throw statements are expressions making them available to use in pretty much all locations.
9+
10+
----------------------------------------------------------------------
11+
Your task is to convert the `if` statement to one line of code, using the ternary operator.
12+
13+
### The advantages of throwing being an exception
14+
15+
* It is possible to throw an exception in a short closure
16+
* It is possible to throw an exception in a ternary or coalesce operation
17+
18+
----------------------------------------------------------------------
19+
## HINTS
20+
21+
Documentation on throwing exception can be found by pointing your browser here:
22+
[https://www.php.net/manual/en/language.exceptions.php]()
23+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
echo str_starts_with($_SERVER['REQUEST_URI'], '/forbidden')
4+
? throw new InvalidArgumentException('Access denied!')
5+
: 'Welcome!';

src/Exercise/ThrowAnExpression.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace PhpSchool\PHP8Appreciate\Exercise;
4+
5+
use Faker\Generator as FakerGenerator;
6+
use GuzzleHttp\Psr7\Request;
7+
use PhpParser\Node\Expr\Ternary;
8+
use PhpParser\Node\Expr\Throw_;
9+
use PhpParser\Node\Stmt;
10+
use PhpParser\Node\Stmt\If_;
11+
use PhpParser\NodeFinder;
12+
use PhpParser\Parser;
13+
use PhpSchool\PhpWorkshop\Exercise\AbstractExercise;
14+
use PhpSchool\PhpWorkshop\Exercise\CgiExercise;
15+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
16+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
17+
use PhpSchool\PhpWorkshop\Exercise\ProvidesInitialCode;
18+
use PhpSchool\PhpWorkshop\Exercise\SubmissionPatchable;
19+
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
20+
use PhpSchool\PhpWorkshop\Input\Input;
21+
use PhpSchool\PhpWorkshop\Patch;
22+
use PhpSchool\PhpWorkshop\Result\Failure;
23+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
24+
use PhpSchool\PhpWorkshop\Result\Success;
25+
use PhpSchool\PhpWorkshop\Solution\SingleFileSolution;
26+
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
27+
28+
class ThrowAnExpression extends AbstractExercise implements
29+
ExerciseInterface,
30+
CgiExercise,
31+
SelfCheck,
32+
ProvidesInitialCode,
33+
SubmissionPatchable
34+
{
35+
public function __construct(private Parser $parser, private FakerGenerator $faker)
36+
{
37+
}
38+
39+
public function getName(): string
40+
{
41+
return 'Throw an Expression';
42+
}
43+
44+
public function getDescription(): string
45+
{
46+
return 'PHP 8\'s throw expression';
47+
}
48+
49+
public function getType(): ExerciseType
50+
{
51+
return ExerciseType::CGI();
52+
}
53+
54+
public function getRequests(): array
55+
{
56+
return [
57+
(new Request('GET', 'https://top-secret.com/forbidden')),
58+
(new Request('GET', 'https://top-secret.com/blog'))
59+
];
60+
}
61+
62+
public function check(Input $input): ResultInterface
63+
{
64+
/** @var array<Stmt> $statements */
65+
$statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program')));
66+
67+
/** @var If_|null $if */
68+
$if = (new NodeFinder())->findFirstInstanceOf($statements, If_::class);
69+
70+
if ($if) {
71+
return Failure::fromNameAndReason($this->getName(), 'If statement found');
72+
}
73+
74+
/** @var Ternary|null $ternary */
75+
$ternary = (new NodeFinder())->findFirstInstanceOf($statements, Ternary::class);
76+
77+
if ($ternary === null) {
78+
return Failure::fromNameAndReason($this->getName(), 'No ternary statement found');
79+
}
80+
81+
if (!$ternary->if instanceof Throw_ && !$ternary->else instanceof Throw_) {
82+
return Failure::fromNameAndReason($this->getName(), 'Ternary does not make use of throw expression');
83+
}
84+
85+
return new Success($this->getName());
86+
}
87+
88+
public function getInitialCode(): SolutionInterface
89+
{
90+
return SingleFileSolution::fromFile(
91+
__DIR__ . '/../../exercises/throw-an-expression/initial/throw-an-expression.php'
92+
);
93+
}
94+
95+
public function getPatch(): Patch
96+
{
97+
return (new Patch())
98+
->withTransformer(new Patch\WrapInTryCatch(\InvalidArgumentException::class));
99+
}
100+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace PhpSchool\PHP8AppreciateTest\Exercise;
4+
5+
use PhpSchool\PHP8Appreciate\Exercise\ThrowAnExpression;
6+
use PhpSchool\PhpWorkshop\Application;
7+
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure;
8+
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure;
9+
use PhpSchool\PhpWorkshop\Result\Cgi\Success;
10+
use PhpSchool\PhpWorkshop\Result\Failure;
11+
use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest;
12+
13+
class ThrowAnExpressionTest extends WorkshopExerciseTest
14+
{
15+
public function getExerciseClass(): string
16+
{
17+
return ThrowAnExpression::class;
18+
}
19+
20+
public function getApplication(): Application
21+
{
22+
return require __DIR__ . '/../../app/bootstrap.php';
23+
}
24+
25+
public function testThrowingWrongException(): void
26+
{
27+
$this->runExercise('wrong-exception.php');
28+
29+
$this->assertVerifyWasNotSuccessful();
30+
31+
$output = $this->getOutputResult();
32+
33+
self::assertInstanceOf(GenericFailure::class, $output->getResults()[0]);
34+
self::assertInstanceOf(Success::class, $output->getResults()[1]);
35+
36+
self::assertMatchesRegularExpression(
37+
'/Fatal error: Uncaught Exception: Access denied!/',
38+
$output->getResults()[0]->getReason()
39+
);
40+
41+
$this->assertOutputWasIncorrect();
42+
}
43+
44+
public function testUsingIfStatement(): void
45+
{
46+
$this->runExercise('if-statement.php');
47+
48+
$this->assertVerifyWasNotSuccessful();
49+
50+
$this->assertResultsHasFailure(Failure::class, 'If statement found');
51+
52+
$this->assertOutputWasCorrect();
53+
}
54+
55+
public function testUsingNoTernary(): void
56+
{
57+
$this->runExercise('no-ternary.php');
58+
59+
$this->assertVerifyWasNotSuccessful();
60+
61+
$this->assertResultsHasFailure(Failure::class, 'No ternary statement found');
62+
63+
$this->assertOutputWasIncorrect();
64+
}
65+
66+
public function testNoThrowExpression(): void
67+
{
68+
$this->runExercise('no-throw-expression.php');
69+
70+
$this->assertVerifyWasNotSuccessful();
71+
72+
$this->assertResultsHasFailure(Failure::class, 'Ternary does not make use of throw expression');
73+
74+
$this->assertOutputWasCorrect();
75+
}
76+
77+
public function testSuccessfulSolution(): void
78+
{
79+
$this->runExercise('solution-correct.php');
80+
81+
$this->assertVerifyWasSuccessful();
82+
$this->assertOutputWasCorrect();
83+
}
84+
85+
public function testSuccessfulSolutionThrowOnElse(): void
86+
{
87+
$this->runExercise('solution-correct-else.php');
88+
89+
$this->assertVerifyWasSuccessful();
90+
$this->assertOutputWasCorrect();
91+
}
92+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
<?php
22

3+
date_default_timezone_set("Europe/London");
4+
error_reporting(E_ALL);
5+
ini_set("display_errors", "1");
36
echo match ($argv[1]) {
47
'enter' => 13 + 10,
58
'up' => 119 + 10,
69
'down' => 73 + 10,
710
'esc' => 27 + 10,
8-
default => 0 + 10
9-
};
11+
default => 0 + 10,
12+
};

0 commit comments

Comments
 (0)