Skip to content

Commit b32e63a

Browse files
committed
WIP
1 parent 19d4382 commit b32e63a

File tree

11 files changed

+231
-53
lines changed

11 files changed

+231
-53
lines changed

app/config.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,24 @@
187187
//Exercise Runners
188188
RunnerManager::class => function (ContainerInterface $c) {
189189
$manager = new RunnerManager();
190-
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class)));
191-
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class)));
190+
$manager->addFactory(new CliRunnerFactory($c->get(EventDispatcher::class), $c->get(\PhpSchool\PhpWorkshop\Process\ProcessFactory::class)));
191+
$manager->addFactory(new CgiRunnerFactory($c->get(EventDispatcher::class), $c->get(\PhpSchool\PhpWorkshop\Process\ProcessFactory::class)));
192192
$manager->addFactory(new CustomVerifyingRunnerFactory());
193193
return $manager;
194194
},
195+
\PhpSchool\PhpWorkshop\Process\ProcessFactory::class => function (ContainerInterface $c) {
196+
return new \PhpSchool\PhpWorkshop\Process\DockerProcessFactory(
197+
$c->get('basePath'),
198+
(new \Symfony\Component\Process\ExecutableFinder())->find('docker'),
199+
$c->get('appName')
200+
);
201+
202+
// return new \PhpSchool\PhpWorkshop\Process\HostProcessFactory(
203+
// (new \Symfony\Component\Process\ExecutableFinder())->find('php'),
204+
// (new \Symfony\Component\Process\ExecutableFinder())->find('php-cgi'),
205+
// '/usr/local/bin/composer',
206+
// );
207+
},
195208

196209
//commands
197210
MenuCommand::class => function (ContainerInterface $c) {
@@ -249,7 +262,9 @@
249262
InitialCodeListener::class => function (ContainerInterface $c) {
250263
return new InitialCodeListener($c->get('currentWorkingDirectory'), $c->get(LoggerInterface::class));
251264
},
252-
PrepareSolutionListener::class => create(),
265+
PrepareSolutionListener::class => function (ContainerInterface $c) {
266+
return new PrepareSolutionListener($c->get(\PhpSchool\PhpWorkshop\Process\ProcessFactory::class));
267+
},
253268
CodePatchListener::class => function (ContainerInterface $c) {
254269
return new CodePatchListener(
255270
$c->get(CodePatcher::class),

src/ExerciseDispatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public function verify(ExerciseInterface $exercise, Input $input): ResultAggrega
135135
}
136136

137137
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('verify.start', $exercise, $input));
138-
138+
139139
$this->validateChecks($this->checksToRunBefore, $exercise);
140140
$this->validateChecks($this->checksToRunAfter, $exercise);
141141

src/ExerciseRunner/CgiRunner.php

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
2020
use PhpSchool\PhpWorkshop\Input\Input;
2121
use PhpSchool\PhpWorkshop\Output\OutputInterface;
22+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
2223
use PhpSchool\PhpWorkshop\Result\Cgi\CgiResult;
2324
use PhpSchool\PhpWorkshop\Result\Cgi\RequestFailure;
2425
use PhpSchool\PhpWorkshop\Result\Cgi\GenericFailure;
2526
use PhpSchool\PhpWorkshop\Result\Cgi\Success;
2627
use PhpSchool\PhpWorkshop\Result\Cgi\ResultInterface as CgiResultInterface;
2728
use PhpSchool\PhpWorkshop\Result\ResultInterface;
29+
use PhpSchool\PhpWorkshop\Utils\ArrayObject;
2830
use PhpSchool\PhpWorkshop\Utils\RequestRenderer;
2931
use Psr\Http\Message\RequestInterface;
3032
use Psr\Http\Message\ResponseInterface;
@@ -49,9 +51,9 @@ class CgiRunner implements ExerciseRunnerInterface
4951
private $eventDispatcher;
5052

5153
/**
52-
* @var string
54+
* @var ProcessFactory
5355
*/
54-
private $phpLocation;
56+
private $processFactory;
5557

5658
/**
5759
* @var array<class-string>
@@ -73,21 +75,13 @@ class CgiRunner implements ExerciseRunnerInterface
7375
*/
7476
public function __construct(
7577
CgiExercise $exercise,
76-
EventDispatcher $eventDispatcher
78+
EventDispatcher $eventDispatcher,
79+
ProcessFactory $processFactory
7780
) {
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-
8881
/** @var CgiExercise&ExerciseInterface $exercise */
8982
$this->eventDispatcher = $eventDispatcher;
9083
$this->exercise = $exercise;
84+
$this->processFactory = $processFactory;
9185
}
9286

9387
/**
@@ -200,28 +194,22 @@ private function getProcess(string $fileName, RequestInterface $request): Proces
200194
{
201195
$env = [
202196
'REQUEST_METHOD' => $request->getMethod(),
203-
'SCRIPT_FILENAME' => $fileName,
197+
'SCRIPT_FILENAME' => '/solution/' . basename($fileName), // TODO: Figure out this path in the container
204198
'REDIRECT_STATUS' => 302,
205199
'QUERY_STRING' => $request->getUri()->getQuery(),
206200
'REQUEST_URI' => $request->getUri()->getPath(),
207201
'XDEBUG_MODE' => 'off',
208202
];
209203

210-
$cgiBinary = sprintf(
211-
'%s -dalways_populate_raw_post_data=-1 -dhtml_errors=0 -dexpose_php=0',
212-
$this->phpLocation
213-
);
214-
215204
$content = $request->getBody()->__toString();
216-
$cmd = sprintf('echo %s | %s', escapeshellarg($content), $cgiBinary);
217205
$env['CONTENT_LENGTH'] = $request->getBody()->getSize();
218206
$env['CONTENT_TYPE'] = $request->getHeaderLine('Content-Type');
219207

220208
foreach ($request->getHeaders() as $name => $values) {
221209
$env[sprintf('HTTP_%s', strtoupper($name))] = implode(", ", $values);
222210
}
223-
224-
return Process::fromShellCommandline($cmd, null, $env, null, 10);
211+
212+
return $this->processFactory->phpCgi(dirname($fileName), $env, $content);
225213
}
226214

227215
/**

src/ExerciseRunner/CliRunner.php

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
1919
use PhpSchool\PhpWorkshop\Input\Input;
2020
use PhpSchool\PhpWorkshop\Output\OutputInterface;
21+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
2122
use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure;
2223
use PhpSchool\PhpWorkshop\Result\Cli\CliResult;
2324
use PhpSchool\PhpWorkshop\Result\Cli\GenericFailure;
2425
use PhpSchool\PhpWorkshop\Result\Cli\Success;
2526
use PhpSchool\PhpWorkshop\Result\Cli\ResultInterface as CliResultInterface;
2627
use PhpSchool\PhpWorkshop\Result\ResultInterface;
2728
use PhpSchool\PhpWorkshop\Utils\ArrayObject;
29+
use Psr\Container\ContainerInterface;
2830
use RuntimeException;
2931
use Symfony\Component\Process\ExecutableFinder;
3032
use Symfony\Component\Process\Process;
@@ -50,9 +52,9 @@ class CliRunner implements ExerciseRunnerInterface
5052
private $eventDispatcher;
5153

5254
/**
53-
* @var string
55+
* @var ProcessFactory
5456
*/
55-
private $phpLocation;
57+
private $processFactory;
5658

5759
/**
5860
* @var array<class-string>
@@ -70,21 +72,12 @@ class CliRunner implements ExerciseRunnerInterface
7072
* @param CliExercise $exercise The exercise to be invoked.
7173
* @param EventDispatcher $eventDispatcher The event dispatcher.
7274
*/
73-
public function __construct(CliExercise $exercise, EventDispatcher $eventDispatcher)
75+
public function __construct(CliExercise $exercise, EventDispatcher $eventDispatcher, ProcessFactory $processFactory)
7476
{
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-
8577
/** @var CliExercise&ExerciseInterface $exercise */
8678
$this->eventDispatcher = $eventDispatcher;
8779
$this->exercise = $exercise;
80+
$this->processFactory = $processFactory;
8881
}
8982

9083
/**
@@ -134,13 +127,7 @@ private function executePhpFile(string $fileName, ArrayObject $args, string $typ
134127
*/
135128
private function getPhpProcess(string $fileName, ArrayObject $args): Process
136129
{
137-
return new Process(
138-
$args->prepend($fileName)->prepend($this->phpLocation)->getArrayCopy(),
139-
dirname($fileName),
140-
['XDEBUG_MODE' => 'off'],
141-
null,
142-
10
143-
);
130+
return $this->processFactory->phpCli(dirname($fileName), $args->getArrayCopy());
144131
}
145132

146133
/**

src/ExerciseRunner/Factory/CgiRunnerFactory.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
1313
use PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner;
1414
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
15+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
1516
use PhpSchool\PhpWorkshop\Utils\RequestRenderer;
1617

1718
/**
@@ -29,12 +30,18 @@ class CgiRunnerFactory implements ExerciseRunnerFactoryInterface
2930
*/
3031
private $eventDispatcher;
3132

33+
/**
34+
* @var ProcessFactory
35+
*/
36+
private $processFactory;
37+
3238
/**
3339
* @param EventDispatcher $eventDispatcher
3440
*/
35-
public function __construct(EventDispatcher $eventDispatcher)
41+
public function __construct(EventDispatcher $eventDispatcher, ProcessFactory $processFactory)
3642
{
3743
$this->eventDispatcher = $eventDispatcher;
44+
$this->processFactory = $processFactory;
3845
}
3946

4047
/**
@@ -66,6 +73,6 @@ public function configureInput(CommandDefinition $commandDefinition): void
6673
*/
6774
public function create(ExerciseInterface $exercise): ExerciseRunnerInterface
6875
{
69-
return new CgiRunner($exercise, $this->eventDispatcher);
76+
return new CgiRunner($exercise, $this->eventDispatcher, $this->processFactory);
7077
}
7178
}

src/ExerciseRunner/Factory/CliRunnerFactory.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
1313
use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner;
1414
use PhpSchool\PhpWorkshop\ExerciseRunner\ExerciseRunnerInterface;
15+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
1516

1617
/**
1718
* Factory class for `CliRunner`
@@ -27,13 +28,19 @@ class CliRunnerFactory implements ExerciseRunnerFactoryInterface
2728
* @var EventDispatcher
2829
*/
2930
private $eventDispatcher;
31+
32+
/**
33+
* @var ProcessFactory
34+
*/
35+
private $processFactory;
3036

3137
/**
3238
* @param EventDispatcher $eventDispatcher
3339
*/
34-
public function __construct(EventDispatcher $eventDispatcher)
40+
public function __construct(EventDispatcher $eventDispatcher, ProcessFactory $processFactory)
3541
{
3642
$this->eventDispatcher = $eventDispatcher;
43+
$this->processFactory = $processFactory;
3744
}
3845

3946
/**
@@ -65,6 +72,6 @@ public function configureInput(CommandDefinition $commandDefinition): void
6572
*/
6673
public function create(ExerciseInterface $exercise): ExerciseRunnerInterface
6774
{
68-
return new CliRunner($exercise, $this->eventDispatcher);
75+
return new CliRunner($exercise, $this->eventDispatcher, $this->processFactory);
6976
}
7077
}

src/Listener/PrepareSolutionListener.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@
77
use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent;
88
use PhpSchool\PhpWorkshop\Exercise\ProvidesSolution;
99
use PhpSchool\PhpWorkshop\Exception\RuntimeException;
10+
use PhpSchool\PhpWorkshop\Process\ProcessFactory;
11+
use PhpSchool\PhpWorkshop\Utils\ArrayObject;
12+
use Symfony\Component\Process\ExecutableFinder;
1013
use Symfony\Component\Process\Process;
1114

1215
/**
1316
* Listener to install composer deps for an exercise solution
1417
*/
1518
class PrepareSolutionListener
1619
{
20+
private ProcessFactory $processFactory;
21+
22+
public function __construct(ProcessFactory $processFactory)
23+
{
24+
$this->processFactory = $processFactory;
25+
}
26+
1727
/**
1828
* Locations for composer executable
1929
*
@@ -44,9 +54,10 @@ public function __invoke(ExerciseRunnerEvent $event): void
4454
//only install if composer.lock file not available
4555

4656
if (!file_exists(sprintf('%s/vendor', $solution->getBaseDirectory()))) {
47-
$process = new Process(
48-
[self::locateComposer(), 'install', '--no-interaction'],
49-
$solution->getBaseDirectory()
57+
$process = $this->processFactory->composer(
58+
$solution->getBaseDirectory(),
59+
'install',
60+
['--no-interaction']
5061
);
5162

5263
try {

src/Process/DockerProcessFactory.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Process;
6+
7+
use Symfony\Component\Process\ExecutableFinder;
8+
use Symfony\Component\Process\Process;
9+
10+
final class DockerProcessFactory implements ProcessFactory
11+
{
12+
private string $basePath;
13+
private string $docker;
14+
private string $projectName;
15+
16+
public function __construct(string $basePath, string $dockerBinaryPath, string $projectName)
17+
{
18+
$this->basePath = $basePath;
19+
$this->docker = $dockerBinaryPath;
20+
$this->projectName = $projectName;
21+
}
22+
23+
private function baseComposeCommand(): array
24+
{
25+
return [
26+
$this->docker,
27+
'compose',
28+
'-p', $this->projectName,
29+
'-f', '.docker/runtime/docker-compose.yml',
30+
'run',
31+
'--rm'
32+
];
33+
}
34+
35+
public function composer(string $solitionPath, string $composerCommand, array $composerArgs): Process
36+
{
37+
return new Process(
38+
[...$this->baseComposeCommand(), 'runtime', ...$composerCommand, ...$composerArgs],
39+
$this->basePath,
40+
['SOLUTION' => $solutionPath],
41+
null,
42+
60
43+
);
44+
}
45+
46+
public function phpCli(string $fileName, array $args): Process
47+
{
48+
return new Process(
49+
[...$this->baseComposeCommand(), 'runtime', 'php', '/solution/' . basename($fileName), ...$args],
50+
$this->basePath,
51+
['SOLUTION' => dirname($fileName)],
52+
null,
53+
10
54+
);
55+
}
56+
57+
public function phpCgi(string $solutionPath, array $env, string $content): Process
58+
{
59+
$env = array_map(function ($key, $value) {
60+
return sprintf('-e %s=%s', $key, $value);
61+
}, array_keys($env), $env);
62+
63+
$command = [
64+
...$this->baseComposeCommand(),
65+
...$env,
66+
'--entrypoint', '/bin/sh -c',
67+
'runtime',
68+
sprintf('echo "%s" | php-cgi', escapeshellarg($content)),
69+
'-dalways_populate_raw_post_data=-1',
70+
'-dhtml_errors=0',
71+
'-dexpose_php=0',
72+
];
73+
74+
return new Process(
75+
$command,
76+
$this->basePath,
77+
['SOLUTION' => $solutionPath],
78+
null,
79+
10
80+
);
81+
}
82+
}

0 commit comments

Comments
 (0)