Skip to content

Commit d289c37

Browse files
committed
Feature: accept Traversable of file paths
This commit adds support for `ColocatedMappingDriver` to accept `Traversable` of file paths. Before it was only possible to accept an array of directory paths. Now, one could provide fine-grained iterator of only needed files to the Driver. Bundles should use Symfony Finder to implement it, since it gives much flexibility to the client code to configure which files should be included and which not.
1 parent df06aca commit d289c37

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

src/Persistence/Mapping/Driver/ColocatedMappingDriver.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Doctrine\Persistence\Mapping\MappingException;
88
use ReflectionClass;
9+
use Traversable;
910

1011
use function array_merge;
1112
use function array_unique;
@@ -21,8 +22,11 @@
2122
*/
2223
trait ColocatedMappingDriver
2324
{
25+
/** @var Traversable<array-key,string> */
26+
private Traversable $filePaths;
27+
2428
/**
25-
* The paths where to look for mapping files.
29+
* The directory paths where to look for mapping files.
2630
*
2731
* @var array<int, string>
2832
*/
@@ -39,7 +43,7 @@ trait ColocatedMappingDriver
3943
protected string $fileExtension = '.php';
4044

4145
/**
42-
* Cache for getAllClassNames().
46+
* Cache for {@see getAllClassNames()}.
4347
*
4448
* @var array<int, string>|null
4549
* @phpstan-var list<class-string>|null
@@ -67,7 +71,7 @@ public function getPaths(): array
6771
}
6872

6973
/**
70-
* Append exclude lookup paths to metadata driver.
74+
* Append exclude lookup paths to a metadata driver.
7175
*
7276
* @param string[] $paths
7377
*/
@@ -120,13 +124,17 @@ public function getAllClassNames(): array
120124
return $this->classNames;
121125
}
122126

123-
if ($this->paths === []) {
127+
if ($this->paths === [] && ! isset($this->filePaths)) {
124128
throw MappingException::pathRequiredForDriver(static::class);
125129
}
126130

127131
$dirFilesIterator = new DirectoryFilesIterator($this->paths, $this->fileExtension);
132+
128133
/** @var iterable<string> $filePathsIterator */
129-
$filePathsIterator = new FilePathNameIterator($dirFilesIterator);
134+
$filePathsIterator = $this->concatIterables(
135+
$this->filePaths ?? [],
136+
new FilePathNameIterator($dirFilesIterator),
137+
);
130138

131139
/** @var array<string,true> $includedFiles */
132140
$includedFiles = [];
@@ -172,4 +180,19 @@ public function getAllClassNames(): array
172180

173181
return $classes;
174182
}
183+
184+
/**
185+
* @param iterable<TKey, T> $iterable1
186+
* @param iterable<TKey, T> $iterable2
187+
*
188+
* @return iterable<TKey, T>
189+
*
190+
* @template TKey
191+
* @template T
192+
*/
193+
private function concatIterables(iterable $iterable1, iterable $iterable2): iterable
194+
{
195+
yield from $iterable1;
196+
yield from $iterable2;
197+
}
175198
}

src/Persistence/Mapping/MappingException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static function classNotFoundInNamespaces(
3030
public static function pathRequiredForDriver(string $driverClassName): self
3131
{
3232
return new self(sprintf(
33-
'Specifying the paths to your entities is required when using %s to retrieve all class names.',
33+
'Specifying source file paths to your entities is required when using %s to retrieve all class names.',
3434
$driverClassName,
3535
));
3636
}

tests/Persistence/Mapping/ColocatedMappingDriverTest.php

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\Tests\Persistence\Mapping;
66

7+
use ArrayIterator;
78
use Doctrine\Persistence\Mapping\ClassMetadata;
89
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
910
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
@@ -12,14 +13,15 @@
1213
use Doctrine\Tests\Persistence\Mapping\_files\colocated\TestClass;
1314
use Generator;
1415
use PHPUnit\Framework\TestCase;
16+
use Traversable;
1517

1618
use function sort;
1719

1820
class ColocatedMappingDriverTest extends TestCase
1921
{
2022
public function testAddGetPaths(): void
2123
{
22-
$driver = $this->createDriver(__DIR__ . '/_files/colocated');
24+
$driver = $this->createDirectoryPathDriver(__DIR__ . '/_files/colocated');
2325
self::assertSame([
2426
__DIR__ . '/_files/colocated',
2527
], $driver->getPaths());
@@ -35,7 +37,7 @@ public function testAddGetPaths(): void
3537

3638
public function testAddGetExcludePaths(): void
3739
{
38-
$driver = $this->createDriver(__DIR__ . '/_files/colocated');
40+
$driver = $this->createDirectoryPathDriver(__DIR__ . '/_files/colocated');
3941
self::assertSame([], $driver->getExcludePaths());
4042

4143
$driver->addExcludePaths(['/test/path1', '/test/path2']);
@@ -48,46 +50,86 @@ public function testAddGetExcludePaths(): void
4850

4951
public function testGetSetFileExtension(): void
5052
{
51-
$driver = $this->createDriver(__DIR__ . '/_files/colocated');
53+
$driver = $this->createDirectoryPathDriver(__DIR__ . '/_files/colocated');
5254
self::assertSame('.php', $driver->getFileExtension());
5355

5456
$driver->setFileExtension('.php1');
5557

5658
self::assertSame('.php1', $driver->getFileExtension());
5759
}
5860

59-
/** @dataProvider pathProvider */
60-
public function testGetAllClassNames(string $path): void
61+
/** @dataProvider directoryPathProvider */
62+
public function testGetAllClassNamesForDirectory(string $dirPath): void
6163
{
62-
$driver = $this->createDriver($path);
64+
$driver = $this->createDirectoryPathDriver($dirPath);
6365

6466
$classes = $driver->getAllClassNames();
6567

6668
sort($classes);
6769
self::assertSame([Entity::class, EntityFixture::class], $classes);
6870
}
6971

72+
public function testGetAllClassNamesForFilePaths(): void
73+
{
74+
$driver = $this->createFilePathsDriver([
75+
__DIR__ . '/_files/colocated/Entity.php',
76+
__DIR__ . '/_files/colocated/TestClass.php',
77+
]);
78+
79+
$classes = $driver->getAllClassNames();
80+
81+
self::assertSame([Entity::class], $classes, 'The driver should only return the class names for the provided file path names, excluding transient class names.');
82+
}
83+
84+
public function testGetAllClassNamesWorksBothForFilePathsAndRetroactivelyAddedDirectoryPaths(): void
85+
{
86+
$driver = $this->createFilePathsDriver([__DIR__ . '/_files/colocated/Entity.php']);
87+
88+
$driver->addPaths([__DIR__ . '/_files/colocated/']);
89+
90+
$classes = $driver->getAllClassNames();
91+
sort($classes);
92+
93+
self::assertSame(
94+
[Entity::class, EntityFixture::class],
95+
$classes,
96+
'The driver should return class names from both the provided file path names and the retroactively added directory paths (these should not be ignored).',
97+
);
98+
}
99+
70100
/** @return Generator<string, array{string}> */
71-
public static function pathProvider(): Generator
101+
public static function directoryPathProvider(): Generator
72102
{
73103
yield 'straigthforward path' => [__DIR__ . '/_files/colocated'];
74104
yield 'winding path' => [__DIR__ . '/../Mapping/_files/colocated'];
75105
}
76106

77-
private function createDriver(string $path): MyDriver
107+
private function createDirectoryPathDriver(string $dirPath): MyDriver
78108
{
79-
return new MyDriver([$path]);
109+
return new MyDriver([$dirPath]);
110+
}
111+
112+
/** @param list<string> $filePaths */
113+
private function createFilePathsDriver(array $filePaths): MyDriver
114+
{
115+
return new MyDriver(new ArrayIterator($filePaths));
80116
}
81117
}
82118

83119
final class MyDriver implements MappingDriver
84120
{
85121
use ColocatedMappingDriver;
86122

87-
/** @param non-empty-list<string> $paths One or multiple paths where mapping classes can be found. */
88-
public function __construct(array $paths)
123+
/** @param non-empty-list<string>|Traversable<string> $paths One or multiple paths where mapping classes can be found. */
124+
public function __construct(iterable $paths)
89125
{
90-
$this->addPaths($paths);
126+
$isFilePaths = $paths instanceof Traversable;
127+
128+
if (! $isFilePaths) {
129+
$this->paths = $paths;
130+
} else {
131+
$this->filePaths = $paths;
132+
}
91133
}
92134

93135
/**

0 commit comments

Comments
 (0)