Open Menu

Self Checking Exercises#^ TOP

As we have seen in the previous articles, you can build your own custom checks. Checks can be used in as many exercises as you want - you could even create a package which consists of common checks you might want to use in your workshops. The check we built in the Creating Simple Checks article is a good example of a reusable check; you might want to include this in all your exercises.

But what if you want to perform a check that you don't think you will use again? You don't really want to create a class to encompass this logic when it is only to be used in one exercise.

Enter the Self Checking feature!

The Self Checking feature allows your exercise to implement an interface which contains one method - check()during the verification process of the student's solution, your method will be called and passed the input arguments passed to our workshop, which will contain the file name of the student's solution. In this method you can do whatever you want: parse the code into an AST using the PhpParser\Parser service, lint it using a third party tool or whatever else you can think of.

To give you an example of how you might use it - we use it here in Learn You PHP! to check that a submission contains an include/require statement as the exercise is teaching how to separate code over multiple files. We want to enforce the student to include a separate file.

Creating a self checking exercise#^ TOP

Creating a self checking exercise requires implementing the interface PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck, adding your check logic and returning a result. Depending on whether you want a success or a failure to be recorded it will be an instance of PhpSchool\PhpWorkshop\Result\SuccessInterface or \PhpSchool\PhpWorkshop\Result\FailureInterface.

The interface looks like this:

<?php

namespace PhpSchool\PhpWorkshop\ExerciseCheck;

use PhpSchool\PhpWorkshop\Result\ResultInterface;
use PhpSchool\PhpWorkshop\Input\Input;

interface SelfCheck
{
    /**
     * The method is passed the absolute file path to the student's solution and should return a result
     * object which indicates the success or not of the check.
     *
     * @param Input $input The command line arguments passed to the command.
     * @return ResultInterface The result of the check.
     */
    public function check(Input $input);
}

You can implement like so:

<?php

class Mean extends AbstractExercise implements ExerciseInterface, CliExercise, SelfCheck
{

    ...omitting methods described in ExerciseInterface

    /**
     * @param Input $input
     * @return ResultInterface
     */
    public function check(Input $input)
    {
        //do some checking with $input

        if ($someResult) {
            return new Success('My Check');
        }

        return new Failure('My Check', "Something didn't go well!");
    }
}

As you can see, you do the checking logic and then return a result object. The result object is used to render the results to the student. In this case the first argument to PhpSchool\PhpWorkshop\Result\Success is the name of the check being performed. The same is true for the failure PhpSchool\PhpWorkshop\Result\Failure, however, it takes an optional second argument which should describe what went wrong.

Learn more about results here.

Example PSR2 self checking exercise#^ TOP

Contrary to what we said earlier (a PSR2 check would be a good candidate for a re-usable check), let's build that as a self check. We will use the already built example workshop as a base - the finished code is available on the self-checking-exercise branch of the tutorial repository.

We will start fresh from the master branch for this tutorial, so if you haven't already got it, git clone it and install the dependencies:

cd projects

git clone git@github.com:php-school/simple-math.git

cd simple-math

composer install

Our check will run the PHP_CodeSniffer tool against the student's solution and report a success or failure based on the result.

1. Require the PHP_CodeSniffer tool as a dependency

composer require squizlabs/php_codesniffer

2. Modify the exercise to implement the SelfCheck interface

Our exercise should look like the following:

<?php

namespace PhpSchool\SimpleMath\Exercise;

use PhpSchool\PhpWorkshop\Exercise\AbstractExercise;
use PhpSchool\PhpWorkshop\Exercise\CliExercise;
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
use PhpSchool\PhpWorkshop\Input\Input;
use PhpSchool\PhpWorkshop\Result\Failure;
use PhpSchool\PhpWorkshop\Result\ResultInterface;
use PhpSchool\PhpWorkshop\Result\Success;

class Mean extends AbstractExercise implements
    ExerciseInterface,
    CliExercise,
    SelfCheck
{

    /**
     * @return string
     */
    public function getName()
    {
        return 'Mean Average';
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return 'Simple Math';
    }

    /**
     * @return array
     */
    public function getArgs()
    {
        $numArgs = rand(0, 10);

        $args = [];
        for ($i = 0; $i < $numArgs; $i ++) {
            $args[] = rand(0, 100);
        }

        return $args;
    }

    /**
     * @return ExerciseType
     */
    public function getType()
    {
        return ExerciseType::CLI();
    }

    /**
     * @param Input $input
     * @return ResultInterface
     */
    public function check(Input $input)
    {

    }
}

3. Implement the check logic

As you can see, our check does nothing at the minute. Let's add the logic to execute phpcs on the student's solution using the PSR2 standard. As we brought in the tool via Composer, we can rest assured that the binary phpcs is available in our projects vendor directory.

Our method might look something like this - nothing new going on:

/**
 * @param Input $input
 * @return ResultInterface
 */
public function check(Input $input)
{
    $phpCsBinary = __DIR__ . '/../../vendor/bin/phpcs';
    $cmd = sprintf('%s %s --standard=PSR2', $phpCsBinary, $input->getArgument('program'));
    exec($cmd, $output, $exitCode);

    if ($exitCode === 0) {
        return new Success('PSR2 Code Check');
    }

    return new Failure('PSR2 Code Check', 'Coding style did not conform to PSR2!');
}

If the phpcs binary returns a non-zero exit code - a failure occurred: probably the solution did not pass the coding standard check. So we return a failure with an error message. Otherwise a Success is returned.

Verifying a solution which does not pass the PSR2 coding standard will yield the output:

And a solution which does pass would yield the output:

Hopefully this feature will help you build your workshops that bit faster!