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.php
This 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.