Skip to content

Commit 1f3dfe9

Browse files
committed
Use new process factory
1 parent 24f8673 commit 1f3dfe9

11 files changed

+116
-217
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/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(

Diff for: src/ExerciseRunner/CliRunner.php

+44-87
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
1919
use PhpSchool\PhpWorkshop\Input\Input;
2020
use PhpSchool\PhpWorkshop\Output\OutputInterface;
21+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
22+
use PhpSchool\PhpWorkshop\Process\ProcessInput;
2123
use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure;
2224
use PhpSchool\PhpWorkshop\Result\Cli\CliResult;
2325
use PhpSchool\PhpWorkshop\Result\Cli\GenericFailure;
2426
use PhpSchool\PhpWorkshop\Result\Cli\Success;
2527
use PhpSchool\PhpWorkshop\Result\Cli\ResultInterface as CliResultInterface;
2628
use PhpSchool\PhpWorkshop\Result\ResultInterface;
2729
use PhpSchool\PhpWorkshop\Utils\ArrayObject;
30+
use PhpSchool\PhpWorkshop\Utils\Collection;
2831
use RuntimeException;
2932
use Symfony\Component\Process\ExecutableFinder;
3033
use Symfony\Component\Process\Process;
@@ -39,25 +42,10 @@
3942
*/
4043
class CliRunner implements ExerciseRunnerInterface
4144
{
42-
/**
43-
* @var CliExercise&ExerciseInterface
44-
*/
45-
private $exercise;
46-
47-
/**
48-
* @var EventDispatcher
49-
*/
50-
private $eventDispatcher;
51-
52-
/**
53-
* @var string
54-
*/
55-
private $phpLocation;
56-
5745
/**
5846
* @var array<class-string>
5947
*/
60-
private static $requiredChecks = [
48+
private static array $requiredChecks = [
6149
FileExistsCheck::class,
6250
CodeExistsCheck::class,
6351
PhpLintCheck::class,
@@ -67,24 +55,13 @@ class CliRunner implements ExerciseRunnerInterface
6755
/**
6856
* Requires the exercise instance and an event dispatcher.
6957
*
70-
* @param CliExercise $exercise The exercise to be invoked.
71-
* @param EventDispatcher $eventDispatcher The event dispatcher.
58+
* @param CliExercise&ExerciseInterface $exercise The exercise to be invoked.
7259
*/
73-
public function __construct(CliExercise $exercise, EventDispatcher $eventDispatcher)
74-
{
75-
$php = (new ExecutableFinder())->find('php');
76-
77-
if (null === $php) {
78-
throw new RuntimeException(
79-
'Could not load php binary. Please install php using your package manager.'
80-
);
81-
}
82-
83-
$this->phpLocation = $php;
84-
85-
/** @var CliExercise&ExerciseInterface $exercise */
86-
$this->eventDispatcher = $eventDispatcher;
87-
$this->exercise = $exercise;
60+
public function __construct(
61+
private CliExercise $exercise,
62+
private EventDispatcher $eventDispatcher,
63+
private ProcessFactory $processFactory
64+
) {
8865
}
8966

9067
/**
@@ -105,59 +82,6 @@ public function getRequiredChecks(): array
10582
return self::$requiredChecks;
10683
}
10784

108-
/**
109-
* @param string $fileName
110-
* @param ArrayObject<int, string> $args
111-
* @param string $type
112-
* @return string
113-
*/
114-
private function executePhpFile(string $fileName, ArrayObject $args, string $type): string
115-
{
116-
$process = $this->getPhpProcess($fileName, $args);
117-
118-
$process->start();
119-
$this->eventDispatcher->dispatch(new CliExecuteEvent(sprintf('cli.verify.%s.executing', $type), $args));
120-
$process->wait();
121-
122-
if (!$process->isSuccessful()) {
123-
throw CodeExecutionException::fromProcess($process);
124-
}
125-
126-
return $process->getOutput();
127-
}
128-
129-
/**
130-
* @param string $fileName
131-
* @param ArrayObject<int, string> $args
132-
*
133-
* @return Process
134-
*/
135-
private function getPhpProcess(string $fileName, ArrayObject $args): Process
136-
{
137-
return new Process(
138-
$args->prepend($fileName)->prepend($this->phpLocation)->getArrayCopy(),
139-
dirname($fileName),
140-
$this->getDefaultEnv() + ['XDEBUG_MODE' => 'off'],
141-
null,
142-
10
143-
);
144-
}
145-
146-
/**
147-
* We need to reset env entirely, because Symfony inherits it. We do that by setting all
148-
* the current env vars to false
149-
*
150-
* @return array<string, false>
151-
*/
152-
private function getDefaultEnv(): array
153-
{
154-
$env = array_map(fn () => false, $_ENV);
155-
$env + array_map(fn () => false, $_SERVER);
156-
157-
return $env;
158-
}
159-
160-
16185
/**
16286
* Verifies a solution by invoking PHP from the CLI passing the arguments gathered from the exercise
16387
* as command line arguments to PHP.
@@ -272,7 +196,12 @@ public function run(Input $input, OutputInterface $output): bool
272196

273197
$args = $event->getArgs();
274198

275-
$process = $this->getPhpProcess($input->getRequiredArgument('program'), $args);
199+
$process = $this->getPhpProcess(
200+
dirname($input->getRequiredArgument('program')),
201+
$input->getRequiredArgument('program'),
202+
$args
203+
);
204+
276205
$process->start();
277206
$this->eventDispatcher->dispatch(
278207
new CliExecuteEvent('cli.run.student.executing', $args, ['output' => $output])
@@ -296,4 +225,32 @@ public function run(Input $input, OutputInterface $output): bool
296225
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cli.run.finish', $this->exercise, $input));
297226
return $success;
298227
}
228+
229+
/**
230+
* @param ArrayObject<int, string> $args
231+
*/
232+
private function executePhpFile(string $fileName, ArrayObject $args, string $type): string
233+
{
234+
$process = $this->getPhpProcess(dirname($fileName), $fileName, $args);
235+
236+
$process->start();
237+
$this->eventDispatcher->dispatch(new CliExecuteEvent(sprintf('cli.verify.%s.executing', $type), $args));
238+
$process->wait();
239+
240+
if (!$process->isSuccessful()) {
241+
throw CodeExecutionException::fromProcess($process);
242+
}
243+
244+
return $process->getOutput();
245+
}
246+
247+
/**
248+
* @param ArrayObject<int, string> $args
249+
*/
250+
private function getPhpProcess(string $workingDirectory, string $fileName, ArrayObject $args): Process
251+
{
252+
return $this->processFactory->create(
253+
new ProcessInput('php', [$fileName, ...$args->getArrayCopy()], $workingDirectory, [])
254+
);
255+
}
299256
}

0 commit comments

Comments
 (0)