1919use PhpSchool \PhpWorkshop \Exercise \ExerciseInterface ;
2020use PhpSchool \PhpWorkshop \Input \Input ;
2121use PhpSchool \PhpWorkshop \Output \OutputInterface ;
22+ use PhpSchool \PhpWorkshop \Process \ProcessFactory ;
23+ use PhpSchool \PhpWorkshop \Process \ProcessInput ;
2224use PhpSchool \PhpWorkshop \Result \Cgi \CgiResult ;
2325use PhpSchool \PhpWorkshop \Result \Cgi \RequestFailure ;
2426use PhpSchool \PhpWorkshop \Result \Cgi \GenericFailure ;
3234use Symfony \Component \Process \ExecutableFinder ;
3335use 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 */
3943class 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