diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..18e14aa --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore diff --git a/src/RepositoryFactory.php b/src/RepositoryFactory.php index bb8c72c..6c1b6a5 100644 --- a/src/RepositoryFactory.php +++ b/src/RepositoryFactory.php @@ -75,7 +75,7 @@ public static function fromConfig(array $storage, string $entityClass): Reposito return match ($driver) { 'file' => new FileRepository( - self::requiredString($storage, 'path', 'file', "'path' => '/var/data/articles.json' — the JSON collection file"), + self::requiredString($storage, 'path', 'file', "'path' => '/var/data/.json' — the JSON collection file"), $entityClass, ), 'sqlite' => new SqliteRepository( @@ -107,8 +107,9 @@ public static function fromConfig(array $storage, string $entityClass): Reposito /** * The non-empty string stored under `$key`, or the teaching error naming the exact key — - * `storage.{$key}` — and a copy-pasteable example. An empty string is as absent as a missing - * key: `''` opens nothing. + * `storage.{$key}` — and an example of its value. The example names nothing of its own: where the + * value is the caller's to name it shows a placeholder, and otherwise only the app itself. An empty + * string is as absent as a missing key: `''` opens nothing. * * @param array $storage */ diff --git a/tests/RepositoryFactoryTest.php b/tests/RepositoryFactoryTest.php index 51ed4b8..8dd1fc9 100644 --- a/tests/RepositoryFactoryTest.php +++ b/tests/RepositoryFactoryTest.php @@ -184,7 +184,7 @@ public function testFileWithoutAPathTeachesTheExactKeyAndAnExample(): void $message = $e->getMessage(); $this->assertStringContainsString('storage.path', $message, 'the error must name the exact missing key'); $this->assertStringContainsString("'file'", $message, 'the error must name the driver that needs the key'); - $this->assertStringContainsString('/var/data/articles.json', $message, 'the error must carry a copy-pasteable example'); + $this->assertStringContainsString("'path' => '/var/data/.json'", $message, 'the error must show what the value looks like'); } } @@ -215,6 +215,32 @@ public function testMysqlWithoutADsnTeachesTheExactKeyAndAnExample(): void } } + /** + * AN EXAMPLE IS NOT THE EXAM (greenhouse decisions/0594 §5, question 7). A teaching error is read by + * whoever configures storage, whatever they are building. Where its example has to name the thing being + * stored into, it names nothing of its own: a placeholder for what is the caller's to name, or the app. + */ + public function testTheExampleOfAValueNamesNothingOfItsOwn(): void + { + $named = []; + foreach (['file' => '~/([^/\']+)\.json\'~', 'sqlite' => '~/([^/\']+)\.db\'~', 'mysql' => '~dbname=([^;\']+)~'] as $driver => $name) { + try { + RepositoryFactory::fromConfig(['driver' => $driver], TestEntity::class); + $this->fail("the '{$driver}' driver without its key must throw the teaching InvalidArgumentException"); + } catch (\InvalidArgumentException $e) { + $this->assertSame(1, preg_match('/\be\.g\. (.+)$/s', $e->getMessage(), $example), "the '{$driver}' error shows an example"); + $this->assertSame(1, preg_match($name, $example[1], $found), "the '{$driver}' example names what it stores into"); + $this->assertTrue( + preg_match('/^<[a-z]+>$/', $found[1]) === 1 || $found[1] === 'app', + "the '{$driver}' example names «{$found[1]}»: it shows a placeholder for what is the caller's to name, or the app", + ); + $named[$driver] = $found[1]; + } + } + + $this->assertSame(['file' => '', 'sqlite' => 'app', 'mysql' => 'app'], $named, 'the control: all three examples were read'); + } + /** An empty-string value is as absent as a missing key — '' opens nothing. */ public function testAnEmptyStringPathIsAsMissingAsNoPath(): void {