1818use PhpSchool \PhpWorkshop \Exercise \ExerciseInterface ;
1919use PhpSchool \PhpWorkshop \Input \Input ;
2020use PhpSchool \PhpWorkshop \Output \OutputInterface ;
21+ use PhpSchool \PhpWorkshop \Process \ProcessFactory ;
22+ use PhpSchool \PhpWorkshop \Process \ProcessInput ;
2123use PhpSchool \PhpWorkshop \Result \Cli \RequestFailure ;
2224use PhpSchool \PhpWorkshop \Result \Cli \CliResult ;
2325use PhpSchool \PhpWorkshop \Result \Cli \GenericFailure ;
2426use PhpSchool \PhpWorkshop \Result \Cli \Success ;
2527use PhpSchool \PhpWorkshop \Result \Cli \ResultInterface as CliResultInterface ;
2628use PhpSchool \PhpWorkshop \Result \ResultInterface ;
2729use PhpSchool \PhpWorkshop \Utils \ArrayObject ;
30+ use PhpSchool \PhpWorkshop \Utils \Collection ;
2831use RuntimeException ;
2932use Symfony \Component \Process \ExecutableFinder ;
3033use Symfony \Component \Process \Process ;
3942 */
4043class 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