Skip to content

Commit 48ad656

Browse files
committed
Allow runners to configure command definitions
1 parent 8ea28cb commit 48ad656

11 files changed

+353
-89
lines changed

app/config.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
use PhpSchool\PhpWorkshop\CodePatcher;
2121
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
2222
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
23+
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CgiRunnerFactory;
24+
use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CliRunnerFactory;
25+
use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager;
2326
use PhpSchool\PhpWorkshop\Factory\EventDispatcherFactory;
2427
use PhpSchool\PhpWorkshop\Factory\MenuFactory;
2528
use PhpSchool\PhpWorkshop\Factory\ResultRendererFactory;
26-
use PhpSchool\PhpWorkshop\Factory\RunnerFactory;
2729
use PhpSchool\PhpWorkshop\Listener\CheckExerciseAssignedListener;
2830
use PhpSchool\PhpWorkshop\Listener\CodePatchListener;
31+
use PhpSchool\PhpWorkshop\Listener\ConfigureCommandListener;
2932
use PhpSchool\PhpWorkshop\Listener\PrepareSolutionListener;
3033
use PhpSchool\PhpWorkshop\Listener\SelfCheckListener;
3134
use PhpSchool\PhpWorkshop\MenuItem\ResetProgress;
@@ -63,7 +66,7 @@
6366
WorkshopType::class => WorkshopType::STANDARD(),
6467
ExerciseDispatcher::class => function (ContainerInterface $c) {
6568
return new ExerciseDispatcher(
66-
$c->get(RunnerFactory::class),
69+
$c->get(RunnerManager::class),
6770
$c->get(ResultAggregator::class),
6871
$c->get(EventDispatcher::class),
6972
$c->get(CheckRepository::class)
@@ -86,8 +89,8 @@
8689
new CommandDefinition('menu', [], MenuCommand::class),
8790
new CommandDefinition('help', [], HelpCommand::class),
8891
new CommandDefinition('print', [], PrintCommand::class),
89-
new CommandDefinition('verify', ['program'], VerifyCommand::class),
90-
new CommandDefinition('run', ['program'], RunCommand::class),
92+
new CommandDefinition('verify', [], VerifyCommand::class),
93+
new CommandDefinition('run', [], RunCommand::class),
9194
new CommandDefinition('credits', [], CreditsCommand::class)
9295
],
9396
'menu',
@@ -117,7 +120,12 @@
117120
EventDispatcherFactory::class => object(),
118121

119122
//Exercise Runners
120-
RunnerFactory::class => object(),
123+
RunnerManager::class => function (ContainerInterface $c) {
124+
$manager = new RunnerManager;
125+
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class)));
126+
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class)));
127+
return $manager;
128+
},
121129

122130
//commands
123131
MenuCommand::class => function (ContainerInterface $c) {
@@ -172,16 +180,23 @@
172180
},
173181

174182
//Listeners
175-
PrepareSolutionListener::class => object(),
176-
CodePatchListener::class => function (ContainerInterface $c) {
183+
PrepareSolutionListener::class => object(),
184+
CodePatchListener::class => function (ContainerInterface $c) {
177185
return new CodePatchListener($c->get(CodePatcher::class));
178186
},
179-
SelfCheckListener::class => function (ContainerInterface $c) {
187+
SelfCheckListener::class => function (ContainerInterface $c) {
180188
return new SelfCheckListener($c->get(ResultAggregator::class));
181189
},
182190
CheckExerciseAssignedListener::class => function (ContainerInterface $c) {
183191
return new CheckExerciseAssignedListener($c->get(UserState::class));
184192
},
193+
ConfigureCommandListener::class => function (ContainerInterface $c) {
194+
return new ConfigureCommandListener(
195+
$c->get(UserState::class),
196+
$c->get(ExerciseRepository::class),
197+
$c->get(RunnerManager::class)
198+
);
199+
},
185200

186201
//checks
187202
FileExistsCheck::class => object(),
@@ -271,6 +286,11 @@
271286
containerListener(CheckExerciseAssignedListener::class)
272287
],
273288
],
289+
'configure-command-arguments' => [
290+
'route.pre.resolve.args' => [
291+
containerListener(ConfigureCommandListener::class)
292+
],
293+
],
274294
'prepare-solution' => [
275295
'verify.start' => [
276296
containerListener(PrepareSolutionListener::class),

src/ExerciseDispatcher.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use PhpSchool\PhpWorkshop\Exception\ExerciseNotConfiguredException;
1313
use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException;
1414
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
15-
use PhpSchool\PhpWorkshop\Factory\RunnerFactory;
15+
use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager;
1616
use PhpSchool\PhpWorkshop\Input\Input;
1717
use PhpSchool\PhpWorkshop\Output\OutputInterface;
1818

@@ -36,7 +36,7 @@ class ExerciseDispatcher
3636
private $checksToRunAfter = [];
3737

3838
/**
39-
* @var RunnerFactory
39+
* @var RunnerManager
4040
*/
4141
private $runnerFactory;
4242

@@ -56,18 +56,18 @@ class ExerciseDispatcher
5656
private $checkRepository;
5757

5858
/**
59-
* @param RunnerFactory $runnerFactory Factory capable of building an exercise runner based on the exercise type.
59+
* @param RunnerManager $runnerManager Factory capable of building an exercise runner based on the exercise type.
6060
* @param ResultAggregator $resultAggregator
6161
* @param EventDispatcher $eventDispatcher
6262
* @param CheckRepository $checkRepository
6363
*/
6464
public function __construct(
65-
RunnerFactory $runnerFactory,
65+
RunnerManager $runnerManager,
6666
ResultAggregator $resultAggregator,
6767
EventDispatcher $eventDispatcher,
6868
CheckRepository $checkRepository
6969
) {
70-
$this->runnerFactory = $runnerFactory;
70+
$this->runnerManager = $runnerManager;
7171
$this->results = $resultAggregator;
7272
$this->eventDispatcher = $eventDispatcher;
7373
$this->checkRepository = $checkRepository;
@@ -130,7 +130,12 @@ public function verify(ExerciseInterface $exercise, Input $input)
130130
{
131131
$exercise->configure($this);
132132

133-
$runner = $this->runnerFactory->create($exercise, $this->eventDispatcher, $this);
133+
$runner = $this->runnerManager->getRunner($exercise);
134+
135+
foreach ($runner->getRequiredChecks() as $requiredCheck) {
136+
$this->requireCheck($requiredCheck);
137+
}
138+
134139
$this->eventDispatcher->dispatch(new Event('verify.start', compact('exercise', 'input')));
135140

136141
$this->validateChecks($this->checksToRunBefore, $exercise);
@@ -179,8 +184,8 @@ public function run(ExerciseInterface $exercise, Input $input, OutputInterface $
179184
$this->eventDispatcher->dispatch(new Event('run.start', compact('exercise', 'input')));
180185

181186
try {
182-
$exitStatus = $this->runnerFactory
183-
->create($exercise, $this->eventDispatcher, $this)
187+
$exitStatus = $this->runnerManager
188+
->getRunner($exercise)
184189
->run($input, $output);
185190
} finally {
186191
$this->eventDispatcher->dispatch(new Event('run.finish', compact('exercise', 'input')));

src/ExerciseRunner/CgiRunner.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,17 @@ public function getName()
9191
}
9292

9393
/**
94-
* Configure the exercise dispatcher. For example set the required checks
95-
* for this exercise type.
94+
* Get an array of the class names of the required checks this runner needs.
9695
*
97-
* @param ExerciseDispatcher $exerciseDispatcher
98-
* @return self
96+
* @return array
9997
*/
100-
public function configure(ExerciseDispatcher $exerciseDispatcher)
98+
public function getRequiredChecks()
10199
{
102-
$exerciseDispatcher->requireCheck(FileExistsCheck::class);
103-
$exerciseDispatcher->requireCheck(PhpLintCheck::class);
104-
$exerciseDispatcher->requireCheck(CodeParseCheck::class);
105-
106-
return $this;
100+
return [
101+
FileExistsCheck::class,
102+
PhpLintCheck::class,
103+
CodeParseCheck::class,
104+
];
107105
}
108106

109107
/**

src/ExerciseRunner/CliRunner.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,17 @@ public function getName()
6464
}
6565

6666
/**
67-
* Configure the exercise dispatcher. For example set the required checks
68-
* for this exercise type.
67+
* Get an array of the class names of the required checks this runner needs.
6968
*
70-
* @param ExerciseDispatcher $exerciseDispatcher
71-
* @return self
69+
* @return array
7270
*/
73-
public function configure(ExerciseDispatcher $exerciseDispatcher)
71+
public function getRequiredChecks()
7472
{
75-
$exerciseDispatcher->requireCheck(FileExistsCheck::class);
76-
$exerciseDispatcher->requireCheck(PhpLintCheck::class);
77-
$exerciseDispatcher->requireCheck(CodeParseCheck::class);
78-
79-
return $this;
73+
return [
74+
FileExistsCheck::class,
75+
PhpLintCheck::class,
76+
CodeParseCheck::class,
77+
];
8078
}
8179

8280
/**

src/ExerciseRunner/ExerciseRunnerInterface.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@ interface ExerciseRunnerInterface
2525
public function getName();
2626

2727
/**
28-
* Configure the exercise dispatcher. For example set the required checks
29-
* for this exercise type.
28+
* Get an array of the class names of the required checks this runner needs.
3029
*
31-
* @param ExerciseDispatcher $exerciseDispatcher
32-
* @return self
30+
* @return array
3331
*/
34-
public function configure(ExerciseDispatcher $exerciseDispatcher);
32+
public function getRequiredChecks();
3533

3634
/**
3735
* Verify a solution to an exercise. Verification involves executing the reference solution
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Factory;
4+
5+
use PhpSchool\PhpWorkshop\CommandArgument;
6+
use PhpSchool\PhpWorkshop\CommandDefinition;
7+
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
8+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
9+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
10+
use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner;
11+
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
12+
13+
/**
14+
* @author Aydin Hassan <[email protected]>
15+
*/
16+
class CgiRunnerFactory implements ExerciseRunnerFactoryInterface
17+
{
18+
/**
19+
* @var string
20+
*/
21+
private static $type = ExerciseType::CGI;
22+
23+
/**
24+
* @var EventDispatcher
25+
*/
26+
private $eventDispatcher;
27+
28+
/**
29+
* @param EventDispatcher $eventDispatcher
30+
*/
31+
public function __construct(EventDispatcher $eventDispatcher)
32+
{
33+
$this->eventDispatcher = $eventDispatcher;
34+
}
35+
36+
/**
37+
* Whether the factory supports this exercise type.
38+
*
39+
* @param ExerciseInterface $exercise
40+
* @return bool
41+
*/
42+
public function supports(ExerciseInterface $exercise)
43+
{
44+
return $exercise->getType()->getValue() === self::$type;
45+
}
46+
47+
/**
48+
* Add any extra required arguments to the command.
49+
*
50+
* @param CommandDefinition $commandDefinition
51+
*/
52+
public function configureInput(CommandDefinition $commandDefinition)
53+
{
54+
$commandDefinition->addArgument(CommandArgument::required('program'));
55+
}
56+
57+
/**
58+
* Create and return an instance of the runner.
59+
*
60+
* @param ExerciseInterface $exercise
61+
* @return ExerciseRunnerInterface
62+
*/
63+
public function create(ExerciseInterface $exercise)
64+
{
65+
return new CgiRunner($exercise, $this->eventDispatcher);
66+
}
67+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\ExerciseRunner\Factory;
4+
5+
use PhpSchool\PhpWorkshop\CommandArgument;
6+
use PhpSchool\PhpWorkshop\CommandDefinition;
7+
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
8+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
9+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
10+
use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner;
11+
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
12+
13+
/**
14+
* @author Aydin Hassan <[email protected]>
15+
*/
16+
class CliRunnerFactory implements ExerciseRunnerFactoryInterface
17+
{
18+
/**
19+
* @var string
20+
*/
21+
private static $type = ExerciseType::CLI;
22+
23+
/**
24+
* @var EventDispatcher
25+
*/
26+
private $eventDispatcher;
27+
28+
/**
29+
* @param EventDispatcher $eventDispatcher
30+
*/
31+
public function __construct(EventDispatcher $eventDispatcher)
32+
{
33+
$this->eventDispatcher = $eventDispatcher;
34+
}
35+
36+
/**
37+
* Whether the factory supports this exercise type.
38+
*
39+
* @param ExerciseInterface $exercise
40+
* @return bool
41+
*/
42+
public function supports(ExerciseInterface $exercise)
43+
{
44+
return $exercise->getType()->getValue() === self::$type;
45+
}
46+
47+
/**
48+
* Add any extra required arguments to the command.
49+
*
50+
* @param CommandDefinition $commandDefinition
51+
*/
52+
public function configureInput(CommandDefinition $commandDefinition)
53+
{
54+
$commandDefinition->addArgument(CommandArgument::required('program'));
55+
}
56+
57+
/**
58+
* Create and return an instance of the runner.
59+
*
60+
* @param ExerciseInterface $exercise
61+
* @return ExerciseRunnerInterface
62+
*/
63+
public function create(ExerciseInterface $exercise)
64+
{
65+
return new CliRunner($exercise, $this->eventDispatcher);
66+
}
67+
}

0 commit comments

Comments
 (0)