Skip to content

Reorganise event hiearchy #287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading