Ready for the next step?
Use the PHP School workshop documentation to build you own workshop and help teach others PHP!
Open Menu
Every CGI & CLI type exercise must have a reference solution. The solution represents a complete working example of how to solve the exercise's problem.
The solution is used for a few things in the workshop:
A solution is represented by an implementation of PhpSchool\PhpWorkshop\Solution\SolutionInterface.
The workshop framework ships with two implementations of
PhpSchool\PhpWorkshop\Solution\SolutionInterface to cover most scenarios:
solution.phpThis is the default used by PhpSchool\PhpWorkshop\Exercise\AbstractExercise. So if you don't override
the getSolution() method, the solution will be a single file named solution.php contained
in the directory exercises/exercise-name/solution.
This would look like the following if you were to manually construct it:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpSchool\PhpWorkshop\Solution\SingleFileSolution;
$solution = SingleFileSolution::fromFile('/path/to/workshop/exercises/exercise-name/solution.php');
var_dump($solution->getEntryPoint());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution.php"
var_dump($solution->getBaseDirectory());
//Outputs: "/path/to/workshop/exercises/exercise-name"
var_dump($solution->hasComposerFile());
//Outputs: "false";
$files = $solution->getFiles();
var_dump(count($files));
//Outputs: 1
$file = $files[0];
var_dump($file->getBaseDirectory());
//Outputs: "/path/to/workshop/exercises/exercise-name"
var_dump($file->getRelativePath());
//Outputs: "solution.php"
var_dump($file->__toString());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution.php"
var_dump($file->getContents());
//Outputs: "contents-of-the-file"
It is possible that your solution contains more than one PHP file. Maybe you have some classes separated into different files, maybe you also pull in some dependencies via Composer. In either case, you should use PhpSchool\PhpWorkshop\Solution\DirectorySolution.
Usage is simple, just pass it the directory and an (optional) entry point. You can also provide an optional list of files to exclude, more on that later. The entry point defaults to solution.php. The following is a depiction of a directory structure and the code to encompass the solution:
/path/to/workshop/exercises/exercise-name/solution ├── SomeClass.php └── solution.php
To return a directory solution when using the PhpSchool\PhpWorkshop\Exercise\AbstractExercise as a base for your exercise you can override the getSolution method with the following:
<?php
public function getSolution()
{
return DirectorySolution::fromDirectory('/path/to/workshop/exercises/exercise-name');
}
This would load any files in the directory given and treat solution.php as the entry point. Constructed manually this might look like:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpSchool\PhpWorkshop\Solution\DirectorySolution;
$solution = DirectorySolution::fromDirectory('/path/to/workshop/exercises/exercise-name/solution');
var_dump($solution->getEntryPoint());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution/solution.php"
var_dump($solution->getBaseDirectory());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution"
var_dump($solution->hasComposerFile());
//Outputs: "false";
$files = $solution->getFiles();
var_dump(count($files));
//Outputs: 2
$file1 = $files[0];
var_dump($file1->getBaseDirectory());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution"
var_dump($file1->getRelativePath());
//Outputs: "index.php"
var_dump($file1->__toString());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution/solution.php"
var_dump($file1->getContents());
//Outputs: "contents-of-the-file"
$file2 = $files[1];
var_dump($file2->getBaseDirectory());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution"
var_dump($file2->getRelativePath());
//Outputs: "SomeClass.php"
var_dump($file2->__toString());
//Outputs: "/path/to/workshop/exercises/exercise-name/solution/SomeClass.php"
var_dump($file2->getContents());
//Outputs: "contents-of-the-file"
If your solution looked like the below where your entry point is named index.php, you can provide the optional third parameter to the static constructor: fromDirectory. It must be the relative path of the file from the solution base directory.
/path/to/workshop/exercises/exercise-name ├── SomeClass.php └── index.php
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpSchool\PhpWorkshop\Solution\DirectorySolution;
$solution = DirectorySolution::fromDirectory('/path/to/workshop/exercises/exercise-name', [], 'index.php');
The convention is for the entry point file to be named solution.php to keep things simple.
DirectorySolution will throw an instance of InvalidArgumentException if the entry point does not exist in the directory given.
The method getFiles is used to find all the files in an solution. One use case is to display the
contents of the files to the student when they have finished an exercise. This way they can compare notes. Sometimes
you may want some files to be excluded from this. Perhaps you don't want the composer.lock file to be
printed to the terminal as this can be quite long. To exclude some files from the solution, simply provide an array
of excludes, relative to the base directory:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpSchool\PhpWorkshop\Solution\DirectorySolution;
$solution = DirectorySolution::fromDirectory(
'/path/to/workshop/exercises/exercise-name/solution',
[
'composer.lock',
'vendor'
]
);
It is not actually necessary to exclude composer.lock or vendor as these are automatically appended to the list of excludes when using the static constructor fromDirectory.
The following files are excluded by default when using the static constructor fromDirectory:
If for some reason you do not want to ignore, say composer.lock but still vendor you can use the __construct method which does not have any default values:
<?php
require __DIR__ . '/vendor/autoload.php';
use PhpSchool\PhpWorkshop\Solution\DirectorySolution;
$solution = new DirectorySolution(
'/path/to/workshop/exercises/exercise-name/solution',
'solution.php',
['vendor']
);
The argument order for __construct and fromDirectory are slightly different. __construct is: $directory, $entryPoint, $excludes. fromDirectory is: $directory, $excludes, $entryPoint.
If you use a library via Composer then you should include the composer.json and composer.lock file in the solution base directory. DirectorySolution will detect the Composer files automatically. If there are Composer files available, the workshop will run a composer install in the solution base directory before invoking the solution.
This means that you don't need to commit the vendor directory for each reference solution.
If the SingleFileSolution or DirectorySolution implementations do not cover your needs, you can create your own by implementing the following interface:
interface SolutionInterface
{
/**
* @return string
*/
public function getEntryPoint();
/**
* @return SolutionFile[]
*/
public function getFiles();
/**
* @return string
*/
public function getBaseDirectory();
/**
* @return bool
*/
public function hasComposerFile();
}
getEntryPoint()This method should return the name of the file which should be the entry point to your solution, in absolute form.
getFiles()This method should return an array of files. Each file should be represented by an instance of PhpSchool\PhpWorkshop\Solution\SolutionFile.
getBaseDirectory()This should return the absolute path to the directory of the solution.
hasComposerFile()This should return a boolean value depending on whether the solution has a composer.lock file present. If it does, before invoking the solution, composer install will be executed in the solution base directory. This saves you having to bundle the vendor directory in your workshop.