Skip to content

Commit

Permalink
Reorganise event hiearchy
Browse files Browse the repository at this point in the history
  • Loading branch information
AydinHassan committed May 17, 2024
1 parent 4488e46 commit 4b35965
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 102 deletions.
20 changes: 12 additions & 8 deletions src/Event/CgiExecuteEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@

namespace PhpSchool\PhpWorkshop\Event;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Input\Input;
use Psr\Http\Message\RequestInterface;

/**
* An event to represent events which occur throughout the verification and running process in
* `\PhpSchool\PhpWorkshop\ExerciseRunner\CgiRunner`.
*/
class CgiExecuteEvent extends Event
class CgiExecuteEvent extends CgiExerciseRunnerEvent
{
/**
* @var RequestInterface
*/
private $request;
private RequestInterface $request;

/**
* @param string $name The event name.
* @param RequestInterface $request The request that will be performed.
* @param array<mixed> $parameters The event parameters.
*/
public function __construct(string $name, RequestInterface $request, array $parameters = [])
{
public function __construct(
string $name,
ExerciseInterface $exercise,
Input $input,
RequestInterface $request,
array $parameters = []
) {
$parameters['request'] = $request;
parent::__construct($name, $parameters);
parent::__construct($name, $exercise, $input, $parameters);
$this->request = $request;
}

Expand Down
11 changes: 11 additions & 0 deletions src/Event/CgiExerciseRunnerEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpSchool\PhpWorkshop\Event;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\Input\Input;

class CgiExerciseRunnerEvent extends ExerciseRunnerEvent
{
}
17 changes: 12 additions & 5 deletions src/Event/CliExecuteEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,35 @@

namespace PhpSchool\PhpWorkshop\Event;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Utils\ArrayObject;

/**
* An event to represent events which occur throughout the verification and running process in
* `\PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner`.
*/
class CliExecuteEvent extends Event
class CliExecuteEvent extends CliExerciseRunnerEvent
{
/**
* @var ArrayObject<int, string>
*/
private $args;
private ArrayObject $args;

/**
* @param string $name The event name.
* @param ArrayObject<int, string> $args The arguments that should be/have been passed to the program.
* @param array<mixed> $parameters The event parameters.
*/
public function __construct(string $name, ArrayObject $args, array $parameters = [])
{
public function __construct(
string $name,
ExerciseInterface $exercise,
Input $input,
ArrayObject $args,
array $parameters = []
) {
$parameters['args'] = $args;
parent::__construct($name, $parameters);
parent::__construct($name, $exercise, $input, $parameters);
$this->args = $args;
}

Expand Down
11 changes: 11 additions & 0 deletions src/Event/CliExerciseRunnerEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpSchool\PhpWorkshop\Event;

use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext;
use PhpSchool\PhpWorkshop\Input\Input;

class CliExerciseRunnerEvent extends ExerciseRunnerEvent
{
}
11 changes: 4 additions & 7 deletions src/Event/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@
*/
class Event implements EventInterface
{
/**
* @var string
*/
private $name;
private string $name;

/**
* @var array<mixed>
*/
protected $parameters;
protected array $parameters;

/**
* @param string $name The event name.
Expand Down Expand Up @@ -52,13 +49,13 @@ public function getParameters(): array
}

/**
* Get a parameter by it's name.
* Get a parameter by its name.
*
* @param string $name The name of the parameter.
* @return mixed The value.
* @throws InvalidArgumentException If the parameter by name does not exist.
*/
public function getParameter(string $name)
public function getParameter(string $name): mixed
{
if (!array_key_exists($name, $this->parameters)) {
throw new InvalidArgumentException(sprintf('Parameter: "%s" does not exist', $name));
Expand Down
13 changes: 2 additions & 11 deletions src/Event/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,20 @@ class EventDispatcher
/**
* @var array<string, array<callable>>
*/
private $listeners = [];
private array $listeners = [];

/**
* @var ResultAggregator
*/
private $resultAggregator;
private ResultAggregator $resultAggregator;

/**
* @param ResultAggregator $resultAggregator
*/
public function __construct(ResultAggregator $resultAggregator)
{
$this->resultAggregator = $resultAggregator;
}

/**
* Dispatch an event. Can be any event object which implements `PhpSchool\PhpWorkshop\Event\EventInterface`.
*
* @param EventInterface $event
* @return EventInterface
*/
public function dispatch(EventInterface $event): EventInterface
{
Expand Down Expand Up @@ -103,9 +97,6 @@ public function removeListener(string $eventName, callable $callback): void
* Insert a verifier callback which will execute at the given event name much like normal listeners.
* A verifier should return an object which implements `PhpSchool\PhpWorkshop\Result\FailureInterface`
* or `PhpSchool\PhpWorkshop\Result\SuccessInterface`. This result object will be added to the result aggregator.
*
* @param string $eventName
* @param callable $verifier
*/
public function insertVerifier(string $eventName, callable $verifier): void
{
Expand Down
4 changes: 2 additions & 2 deletions src/Event/EventInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ public function getName(): string;
public function getParameters(): array;

/**
* Get a parameter by it's name.
* Get a parameter by its name.
*
* @param string $name The name of the parameter.
* @return mixed The value.
* @throws InvalidArgumentException If the parameter by name does not exist.
*/
public function getParameter(string $name);
public function getParameter(string $name): mixed;
}
130 changes: 81 additions & 49 deletions src/ExerciseRunner/CgiRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PhpSchool\PhpWorkshop\Check\FileExistsCheck;
use PhpSchool\PhpWorkshop\Check\PhpLintCheck;
use PhpSchool\PhpWorkshop\Event\CgiExecuteEvent;
use PhpSchool\PhpWorkshop\Event\CgiExerciseRunnerEvent;
use PhpSchool\PhpWorkshop\Event\Event;
use PhpSchool\PhpWorkshop\Event\EventDispatcher;
use PhpSchool\PhpWorkshop\Event\ExerciseRunnerEvent;
Expand Down Expand Up @@ -85,33 +86,84 @@ public function getRequiredChecks(): array
}

/**
* @param RequestInterface $request
* @param string $fileName
* @return CgiResultInterface
* Verifies a solution by invoking PHP via the `php-cgi` binary, populating all the super globals with
* the information from the request objects returned from the exercise. The exercise can return multiple
* requests so the solution will be invoked for however many requests there are.
*
* Events dispatched (for each request):
*
* * cgi.verify.reference-execute.pre
* * cgi.verify.reference.executing
* * cgi.verify.reference-execute.fail (if the reference solution fails to execute)
* * cgi.verify.student-execute.pre
* * cgi.verify.student.executing
* * cgi.verify.student-execute.fail (if the student's solution fails to execute)
*
* @param Input $input The command line arguments passed to the command.
* @return CgiResult The result of the check.
*/
private function checkRequest(RequestInterface $request, string $fileName): CgiResultInterface
public function verify(Input $input): ResultInterface
{
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.start', $this->exercise, $input));
$result = new CgiResult(
array_map(
function (RequestInterface $request) use ($input) {
return $this->doVerify($request, $input);
},
$this->exercise->getRequests()
)
);
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $input));
return $result;
}

private function doVerify(RequestInterface $request, Input $input): CgiResultInterface
{
try {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.verify.reference-execute.pre', $request)
new CgiExecuteEvent('cgi.verify.reference-execute.pre', $this->exercise, $input, $request)
);
$solutionResponse = $this->executePhpFile(
$input,
$this->exercise->getSolution()->getEntryPoint()->getAbsolutePath(),
$event->getRequest(),
'reference'
);
} catch (CodeExecutionException $e) {
$this->eventDispatcher->dispatch(new Event('cgi.verify.reference-execute.fail', ['exception' => $e]));
$this->eventDispatcher->dispatch(
new CgiExecuteEvent(
'cgi.verify.reference-execute.fail',
$this->exercise,
$input,
$request,
['exception' => $e]
)
);
throw new SolutionExecutionException($e->getMessage());
}

try {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(new CgiExecuteEvent('cgi.verify.student-execute.pre', $request));
$userResponse = $this->executePhpFile($fileName, $event->getRequest(), 'student');
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.verify.student-execute.pre', $this->exercise, $input, $request)
);
$userResponse = $this->executePhpFile(
$input,
$input->getRequiredArgument('program'),
$event->getRequest(),
'student'
);
} catch (CodeExecutionException $e) {
$this->eventDispatcher->dispatch(new Event('cgi.verify.student-execute.fail', ['exception' => $e]));
$this->eventDispatcher->dispatch(
new CgiExecuteEvent(
'cgi.verify.student-execute.fail',
$this->exercise,
$input,
$request,
['exception' => $e]
)
);
return GenericFailure::fromRequestAndCodeExecutionFailure($request, $e);
}

Expand Down Expand Up @@ -146,12 +198,18 @@ private function getHeaders(ResponseInterface $response): array
* @param string $type
* @return ResponseInterface
*/
private function executePhpFile(string $fileName, RequestInterface $request, string $type): ResponseInterface
{
private function executePhpFile(
Input $input,
string $fileName,
RequestInterface $request,
string $type
): ResponseInterface {
$process = $this->getPhpProcess(dirname($fileName), basename($fileName), $request);

$process->start();
$this->eventDispatcher->dispatch(new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $request));
$this->eventDispatcher->dispatch(
new CgiExecuteEvent(sprintf('cgi.verify.%s.executing', $type), $this->exercise, $input, $request)
);
$process->wait();

if (!$process->isSuccessful()) {
Expand Down Expand Up @@ -206,38 +264,6 @@ private function getPhpProcess(string $workingDirectory, string $fileName, Reque
return $this->processFactory->create($processInput);
}

/**
* Verifies a solution by invoking PHP via the `php-cgi` binary, populating all the super globals with
* the information from the request objects returned from the exercise. The exercise can return multiple
* requests so the solution will be invoked for however many requests there are.
*
* Events dispatched (for each request):
*
* * cgi.verify.reference-execute.pre
* * cgi.verify.reference.executing
* * cgi.verify.reference-execute.fail (if the reference solution fails to execute)
* * cgi.verify.student-execute.pre
* * cgi.verify.student.executing
* * cgi.verify.student-execute.fail (if the student's solution fails to execute)
*
* @param Input $input The command line arguments passed to the command.
* @return CgiResult The result of the check.
*/
public function verify(Input $input): ResultInterface
{
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.verify.start', $this->exercise, $input));
$result = new CgiResult(
array_map(
function (RequestInterface $request) use ($input) {
return $this->checkRequest($request, $input->getRequiredArgument('program'));
},
$this->exercise->getRequests()
)
);
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.verify.finish', $this->exercise, $input));
return $result;
}

/**
* Runs a student's solution by invoking PHP via the `php-cgi` binary, populating all the super globals with
* the information from the request objects returned from the exercise. The exercise can return multiple
Expand All @@ -257,12 +283,12 @@ function (RequestInterface $request) use ($input) {
*/
public function run(Input $input, OutputInterface $output): bool
{
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.run.start', $this->exercise, $input));
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.start', $this->exercise, $input));
$success = true;
foreach ($this->exercise->getRequests() as $i => $request) {
/** @var CgiExecuteEvent $event */
$event = $this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student-execute.pre', $request)
new CgiExecuteEvent('cgi.run.student-execute.pre', $this->exercise, $input, $request)
);
$process = $this->getPhpProcess(
dirname($input->getRequiredArgument('program')),
Expand All @@ -272,7 +298,13 @@ public function run(Input $input, OutputInterface $output): bool

$process->start();
$this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student.executing', $request, ['output' => $output])
new CgiExecuteEvent(
'cgi.run.student.executing',
$this->exercise,
$input,
$request,
['output' => $output]
)
);
$process->wait(function ($outputType, $outputBuffer) use ($output) {
$output->write($outputBuffer);
Expand All @@ -286,10 +318,10 @@ public function run(Input $input, OutputInterface $output): bool
$output->lineBreak();

$this->eventDispatcher->dispatch(
new CgiExecuteEvent('cgi.run.student-execute.post', $request)
new CgiExecuteEvent('cgi.run.student-execute.post', $this->exercise, $input, $request)
);
}
$this->eventDispatcher->dispatch(new ExerciseRunnerEvent('cgi.run.finish', $this->exercise, $input));
$this->eventDispatcher->dispatch(new CgiExerciseRunnerEvent('cgi.run.finish', $this->exercise, $input));
return $success;
}
}
Loading

0 comments on commit 4b35965

Please sign in to comment.