Skip to content

Commit 4488e46

Browse files
authored
Merge pull request #286 from php-school/05-05-use_new_process_factory
Use new process factory
2 parents a009c67 + 9cdd0ec commit 4488e46

24 files changed

+231
-282
lines changed

Diff for: app/config.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
use PhpSchool\PhpWorkshop\Output\OutputInterface;
7272
use PhpSchool\PhpWorkshop\Output\StdOutput;
7373
use PhpSchool\PhpWorkshop\Patch;
74+
use PhpSchool\PhpWorkshop\Process\HostProcessFactory;
75+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
7476
use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult;
7577
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure as CgiGenericFailure;
7678
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure as CgiRequestFailure;
@@ -187,12 +189,16 @@
187189
//Exercise Runners
188190
RunnerManager::class => function (ContainerInterface $c) {
189191
$manager = new RunnerManager();
190-
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class)));
191-
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class)));
192+
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class)));
193+
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class), $c->get(ProcessFactory::class)));
192194
$manager->addFactory(new CustomVerifyingRunnerFactory());
193195
return $manager;
194196
},
195197

198+
ProcessFactory::class => function (ContainerInterface $c) {
199+
return new HostProcessFactory();
200+
},
201+
196202
//commands
197203
MenuCommand::class => function (ContainerInterface $c) {
198204
return new MenuCommand($c->get('menu'));

Diff for: src/Exercise/AbstractExercise.php

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace PhpSchool\PhpWorkshop\Exercise;
66

7+
use PhpSchool\PhpWorkshop\Check\FileComparisonCheck;
8+
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
79
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
810
use PhpSchool\PhpWorkshop\Solution\SingleFileSolution;
911
use PhpSchool\PhpWorkshop\Solution\SolutionInterface;
@@ -78,12 +80,14 @@ public static function normaliseName(string $name): string
7880
}
7981

8082
/**
81-
* This method is implemented as empty by default, if you want to add additional checks or listen
82-
* to events, you should override this method.
83-
*
84-
* @param ExerciseDispatcher $dispatcher
83+
* @return list<class-string>
8584
*/
86-
public function configure(ExerciseDispatcher $dispatcher): void
85+
public function getRequiredChecks(): array
86+
{
87+
return [];
88+
}
89+
90+
public function defineListeners(EventDispatcher $dispatcher): void
8791
{
8892
}
8993
}

Diff for: src/Exercise/ExerciseInterface.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpSchool\PhpWorkshop\Exercise;
66

7+
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
78
use PhpSchool\PhpWorkshop\ExerciseDispatcher;
89

910
/**
@@ -34,14 +35,16 @@ public function getType(): ExerciseType;
3435
public function getProblem(): string;
3536

3637
/**
37-
* This is where the exercise specifies the extra checks it may require. It is also
38-
* possible to grab the event dispatcher from the exercise dispatcher and listen to any
39-
* events. This method is automatically invoked just before verifying/running an student's solution
40-
* to an exercise.
38+
* Subscribe to events triggered throughout the verification process
39+
*/
40+
public function defineListeners(EventDispatcher $dispatcher): void;
41+
42+
/**
43+
* This is where the exercise specifies the extra checks it may require.
4144
*
42-
* @param ExerciseDispatcher $dispatcher
45+
* @return array<class-string>
4346
*/
44-
public function configure(ExerciseDispatcher $dispatcher): void;
47+
public function getRequiredChecks(): array;
4548

4649
/**
4750
* A short description of the exercise.

Diff for: src/ExerciseDispatcher.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ public function requireCheck(string $requiredCheck): void
129129
*/
130130
public function verify(ExerciseInterface $exercise, Input $input): ResultAggregator
131131
{
132-
$exercise->configure($this);
133-
134132
$runner = $this->runnerManager->getRunner($exercise);
135133

136-
foreach ($runner->getRequiredChecks() as $requiredCheck) {
134+
$exercise->defineListeners($this->eventDispatcher);
135+
136+
foreach ([...$runner->getRequiredChecks(), ...$exercise->getRequiredChecks()] as $requiredCheck) {
137137
$this->requireCheck($requiredCheck);
138138
}
139139

@@ -181,7 +181,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega
181181
*/
182182
public function run(ExerciseInterface $exercise, Input $input, OutputInterface $output): bool
183183
{
184-
$exercise->configure($this);
184+
$exercise->defineListeners($this->eventDispatcher);
185185

186186
/** @var PhpLintCheck $lint */
187187
$lint = $this->checkRepository->getByClass(PhpLintCheck::class);

Diff for: src/ExerciseRunner/CgiRunner.php

+31-60
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
2020
use PhpSchool\PhpWorkshop\Input\Input;
2121
use PhpSchool\PhpWorkshop\Output\OutputInterface;
22+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
23+
use PhpSchool\PhpWorkshop\Process\ProcessInput;
2224
use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult;
2325
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure;
2426
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure;
@@ -32,31 +34,18 @@
3234
use Symfony\Component\Process\ExecutableFinder;
3335
use Symfony\Component\Process\Process;
3436

37+
use function PHPStan\dumpType;
38+
3539
/**
3640
* The `CGI` runner. This runner executes solutions as if they were behind a web-server. They populate the `$_SERVER`,
3741
* `$_GET` & `$_POST` super globals with information based of the request objects returned from the exercise.
3842
*/
3943
class CgiRunner implements ExerciseRunnerInterface
4044
{
41-
/**
42-
* @var CgiExercise&ExerciseInterface
43-
*/
44-
private $exercise;
45-
46-
/**
47-
* @var EventDispatcher
48-
*/
49-
private $eventDispatcher;
50-
51-
/**
52-
* @var string
53-
*/
54-
private $phpLocation;
55-
5645
/**
5746
* @var array<class-string>
5847
*/
59-
private static $requiredChecks = [
48+
private static array $requiredChecks = [
6049
FileExistsCheck::class,
6150
CodeExistsCheck::class,
6251
PhpLintCheck::class,
@@ -68,26 +57,13 @@ class CgiRunner implements ExerciseRunnerInterface
6857
* be available. It will check for it's existence in the system's $PATH variable or the same
6958
* folder that the CLI php binary lives in.
7059
*
71-
* @param CgiExercise $exercise The exercise to be invoked.
72-
* @param EventDispatcher $eventDispatcher The event dispatcher.
60+
* @param CgiExercise&ExerciseInterface $exercise The exercise to be invoked.
7361
*/
7462
public function __construct(
75-
CgiExercise $exercise,
76-
EventDispatcher $eventDispatcher
63+
private CgiExercise $exercise,
64+
private EventDispatcher $eventDispatcher,
65+
private ProcessFactory $processFactory
7766
) {
78-
$php = (new ExecutableFinder())->find('php-cgi');
79-
80-
if (null === $php) {
81-
throw new RuntimeException(
82-
'Could not load php-cgi binary. Please install php using your package manager.'
83-
);
84-
}
85-
86-
$this->phpLocation = $php;
87-
88-
/** @var CgiExercise&ExerciseInterface $exercise */
89-
$this->eventDispatcher = $eventDispatcher;
90-
$this->exercise = $exercise;
9167
}
9268

9369
/**
@@ -172,7 +148,7 @@ private function getHeaders(ResponseInterface $response): array
172148
*/
173149
private function executePhpFile(string $fileName, RequestInterface $request, string $type): ResponseInterface
174150
{
175-
$process = $this->getProcess($fileName, $request);
151+
$process = $this->getPhpProcess(dirname($fileName), basename($fileName), $request);
176152

177153
$process->start();
178154
$this->eventDispatcher->dispatch(new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $request));
@@ -196,47 +172,38 @@ private function executePhpFile(string $fileName, RequestInterface $request, str
196172
* @param RequestInterface $request
197173
* @return Process
198174
*/
199-
private function getProcess(string $fileName, RequestInterface $request): Process
175+
private function getPhpProcess(string $workingDirectory, string $fileName, RequestInterface $request): Process
200176
{
201-
$env = $this->getDefaultEnv();
202-
$env += [
177+
$env = [
203178
'REQUEST_METHOD' => $request->getMethod(),
204179
'SCRIPT_FILENAME' => $fileName,
205-
'REDIRECT_STATUS' => 302,
180+
'REDIRECT_STATUS' => '302',
206181
'QUERY_STRING' => $request->getUri()->getQuery(),
207182
'REQUEST_URI' => $request->getUri()->getPath(),
208183
'XDEBUG_MODE' => 'off',
209184
];
210185

211-
$cgiBinary = sprintf(
212-
'%s -dalways_populate_raw_post_data=-1 -dhtml_errors=0 -dexpose_php=0',
213-
$this->phpLocation
214-
);
215-
216186
$content = $request->getBody()->__toString();
217-
$cmd = sprintf('echo %s | %s', escapeshellarg($content), $cgiBinary);
218-
$env['CONTENT_LENGTH'] = $request->getBody()->getSize();
187+
$env['CONTENT_LENGTH'] = (string) $request->getBody()->getSize();
219188
$env['CONTENT_TYPE'] = $request->getHeaderLine('Content-Type');
220189

221190
foreach ($request->getHeaders() as $name => $values) {
222191
$env[sprintf('HTTP_%s', strtoupper($name))] = implode(", ", $values);
223192
}
224193

225-
return Process::fromShellCommandline($cmd, null, $env, null, 10);
226-
}
227-
228-
/**
229-
* We need to reset env entirely, because Symfony inherits it. We do that by setting all
230-
* the current env vars to false
231-
*
232-
* @return array<string, false>
233-
*/
234-
private function getDefaultEnv(): array
235-
{
236-
$env = array_map(fn () => false, $_ENV);
237-
$env + array_map(fn () => false, $_SERVER);
194+
$processInput = new ProcessInput(
195+
'php-cgi',
196+
[
197+
'-dalways_populate_raw_post_data=-1',
198+
'-dhtml_errors=0',
199+
'-dexpose_php=0',
200+
],
201+
$workingDirectory,
202+
$env,
203+
$content
204+
);
238205

239-
return $env;
206+
return $this->processFactory->create($processInput);
240207
}
241208

242209
/**
@@ -297,7 +264,11 @@ public function run(Input $input, OutputInterface $output): bool
297264
$event = $this->eventDispatcher->dispatch(
298265
new CgiExecuteEvent('cgi.run.student-execute.pre', $request)
299266
);
300-
$process = $this->getProcess($input->getRequiredArgument('program'), $event->getRequest());
267+
$process = $this->getPhpProcess(
268+
dirname($input->getRequiredArgument('program')),
269+
$input->getRequiredArgument('program'),
270+
$event->getRequest()
271+
);
301272

302273
$process->start();
303274
$this->eventDispatcher->dispatch(

0 commit comments

Comments
 (0)