55namespace PhpSchool \PhpWorkshop \Check ;
66
77use PDO ;
8- use PDOException ;
8+ use PhpSchool \ PhpWorkshop \ Event \ CgiExerciseRunnerEvent ;
99use PhpSchool \PhpWorkshop \Event \CliExecuteEvent ;
10+ use PhpSchool \PhpWorkshop \Event \CliExerciseRunnerEvent ;
1011use PhpSchool \PhpWorkshop \Event \Event ;
1112use PhpSchool \PhpWorkshop \Event \EventDispatcher ;
12- use PhpSchool \PhpWorkshop \Exercise \ TemporaryDirectoryTrait ;
13+ use PhpSchool \PhpWorkshop \Event \ ExerciseRunnerEvent ;
1314use PhpSchool \PhpWorkshop \ExerciseCheck \DatabaseExerciseCheck ;
1415use PhpSchool \PhpWorkshop \Result \Failure ;
1516use PhpSchool \PhpWorkshop \Result \Success ;
16- use RuntimeException ;
17+ use PhpSchool \PhpWorkshop \Utils \Path ;
18+ use PhpSchool \PhpWorkshop \Utils \System ;
19+ use Symfony \Component \Filesystem \Filesystem ;
1720
1821/**
1922 * This check sets up a database and a `PDO` object. It prepends the database DSN as a CLI argument to the student's
2326 */
2427class DatabaseCheck implements ListenableCheckInterface
2528{
26- use TemporaryDirectoryTrait;
29+ private Filesystem $ filesystem ;
30+ private ?string $ dbContent = null ;
2731
28- private string $ databaseDirectory ;
29- private string $ userDatabasePath ;
30- private string $ solutionDatabasePath ;
31- private string $ userDsn ;
32- private string $ solutionDsn ;
33-
34- /**
35- * Setup paths and DSN's.
36- */
37- public function __construct ()
32+ public function __construct (Filesystem $ filesystem = null )
3833 {
39- $ this ->databaseDirectory = $ this ->getTemporaryPath ();
40- $ this ->userDatabasePath = sprintf ('%s/user-db.sqlite ' , $ this ->databaseDirectory );
41- $ this ->solutionDatabasePath = sprintf ('%s/solution-db.sqlite ' , $ this ->databaseDirectory );
42- $ this ->solutionDsn = sprintf ('sqlite:%s ' , $ this ->solutionDatabasePath );
43- $ this ->userDsn = sprintf ('sqlite:%s ' , $ this ->userDatabasePath );
34+ $ this ->filesystem = $ filesystem ? $ filesystem : new Filesystem ();
4435 }
4536
4637 /**
@@ -64,78 +55,69 @@ public function getExerciseInterface(): string
6455 */
6556 public function attach (EventDispatcher $ eventDispatcher ): void
6657 {
67- if (file_exists ($ this ->databaseDirectory )) {
68- throw new RuntimeException (
69- sprintf ('Database directory: "%s" already exists ' , $ this ->databaseDirectory ),
70- );
71- }
72-
73- mkdir ($ this ->databaseDirectory , 0777 , true );
74-
75- try {
76- $ db = new PDO ($ this ->userDsn );
77- $ db ->setAttribute (PDO ::ATTR_ERRMODE , PDO ::ERRMODE_EXCEPTION );
78- } catch (PDOException $ e ) {
79- rmdir ($ this ->databaseDirectory );
80- throw $ e ;
81- }
82-
83- $ eventDispatcher ->listen ('verify.start ' , function (Event $ e ) use ($ db ) {
84- /** @var DatabaseExerciseCheck $exercise */
85- $ exercise = $ e ->getParameter ('exercise ' );
86- $ exercise ->seed ($ db );
87- //make a copy - so solution can modify without effecting database user has access to
88- copy ($ this ->userDatabasePath , $ this ->solutionDatabasePath );
89- });
58+ $ eventDispatcher ->listen (['verify.start ' , 'run.start ' ], function (Event $ e ) {
59+ $ path = System::randomTempPath ('sqlite ' );
9060
91- $ eventDispatcher ->listen ('run.start ' , function (Event $ e ) use ($ db ) {
92- /** @var DatabaseExerciseCheck $exercise */
93- $ exercise = $ e ->getParameter ('exercise ' );
94- $ exercise ->seed ($ db );
95- });
61+ $ this ->filesystem ->touch ($ path );
62+
63+ try {
64+ $ db = $ this ->getPDO ($ path );
65+
66+ /** @var DatabaseExerciseCheck $exercise */
67+ $ exercise = $ e ->getParameter ('exercise ' );
68+ $ exercise ->seed ($ db );
9669
97- $ eventDispatcher ->listen ('cli.verify.reference-execute.pre ' , function (CliExecuteEvent $ e ) {
98- $ e ->prependArg ($ this ->solutionDsn );
70+ $ this ->dbContent = (string ) file_get_contents ($ path );
71+ } finally {
72+ unset($ db );
73+
74+ $ this ->filesystem ->remove ($ path );
75+ }
9976 });
10077
10178 $ eventDispatcher ->listen (
102- ['cli.verify.student-execute.pre ' , 'cli.run.student-execute.pre ' ],
103- function (CliExecuteEvent $ e ) {
104- $ e ->prependArg ($ this ->userDsn );
79+ ['cli.verify.prepare ' , 'cgi.verify.prepare ' ],
80+ function (CliExerciseRunnerEvent |CgiExerciseRunnerEvent $ e ) {
81+ $ e ->getScenario ()->withFile ('db.sqlite ' , (string ) $ this ->dbContent );
82+
83+ $ this ->dbContent = null ;
10584 },
10685 );
10786
108- $ eventDispatcher ->insertVerifier ( ' verify.finish ' , function ( Event $ e ) use ( $ db ) {
109- /** @var DatabaseExerciseCheck $exercise */
110- $ exercise = $ e ->getParameter ( ' exercise ' );
111- $ verifyResult = $ exercise -> verify ( $ db );
87+ $ eventDispatcher ->listen (
88+ ' cli.verify.reference-execute.pre ' ,
89+ fn ( CliExecuteEvent $ e ) => $ e ->prependArg ( ' sqlite:db.sqlite ' ),
90+ );
11291
113- if (false === $ verifyResult ) {
114- return Failure::fromNameAndReason ($ this ->getName (), 'Database verification failed ' );
115- }
92+ $ eventDispatcher ->listen (
93+ ['cli.verify.student-execute.pre ' , 'cli.run.student-execute.pre ' ],
94+ fn (CliExecuteEvent $ e ) => $ e ->prependArg ('sqlite:db.sqlite ' ),
95+ );
11696
117- return new Success ( ' Database Verification Check ' );
118- } );
97+ $ eventDispatcher -> insertVerifier ( ' verify.finish ' , function ( ExerciseRunnerEvent $ e ) {
98+ $ db = $ this -> getPDO (Path:: join ( $ e -> getContext ()-> getStudentExecutionDirectory (), ' db.sqlite ' ) );
11999
120- $ eventDispatcher ->listen (
121- [
122- 'cli.verify.reference-execute.fail ' ,
123- 'verify.finish ' ,
124- 'run.finish ' ,
125- ],
126- function () use ($ db ) {
100+ try {
101+ /** @var DatabaseExerciseCheck $exercise */
102+ $ exercise = $ e ->getParameter ('exercise ' );
103+ $ verifyResult = $ exercise ->verify ($ db );
104+
105+ if (false === $ verifyResult ) {
106+ return Failure::fromNameAndReason ($ this ->getName (), 'Database verification failed ' );
107+ }
108+
109+ return new Success ('Database Verification Check ' );
110+ } finally {
127111 unset($ db );
128- $ this ->unlink ($ this ->userDatabasePath );
129- $ this ->unlink ($ this ->solutionDatabasePath );
130- rmdir ($ this ->databaseDirectory );
131- },
132- );
112+ }
113+ });
133114 }
134115
135- private function unlink (string $ file ): void
116+ private function getPDO (string $ path ): PDO
136117 {
137- if (file_exists ($ file )) {
138- unlink ($ file );
139- }
118+ $ db = new PDO ('sqlite: ' . $ path );
119+ $ db ->setAttribute (PDO ::ATTR_ERRMODE , PDO ::ERRMODE_EXCEPTION );
120+
121+ return $ db ;
140122 }
141123}
0 commit comments