Skip to content

Commit a241c82

Browse files
committed
Feature: accept iterable of file paths
This commit adds support for `ColocatedMappingDriver` to accept `iterable` of file paths. Before it was only possible to accept an array of directory paths. Now, one could provide fine-grained iterator of only necessary 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. Backward compatibility is achieved with the following approach: If it's an array, then it should be OK to take a look at `$paths[0]` and determine if it is a file or a directory. If it's not an array, we can assume that it's `$filePaths` iterable that's given.
1 parent df06aca commit a241c82

File tree

3 files changed

+82
-18
lines changed

3 files changed

+82
-18
lines changed

src/Persistence/Mapping/Driver/ColocatedMappingDriver.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
*/
2222
trait ColocatedMappingDriver
2323
{
24+
/** @var iterable<array-key,string> */
25+
private iterable $filePaths;
26+
2427
/**
25-
* The paths where to look for mapping files.
28+
* The directory paths where to look for mapping files.
2629
*
2730
* @var array<int, string>
2831
*/
@@ -39,7 +42,7 @@ trait ColocatedMappingDriver
3942
protected string $fileExtension = '.php';
4043

4144
/**
42-
* Cache for getAllClassNames().
45+
* Cache for {@see getAllClassNames()}.
4346
*
4447
* @var array<int, string>|null
4548
* @phpstan-var list<class-string>|null
@@ -67,7 +70,7 @@ public function getPaths(): array
6770
}
6871

6972
/**
70-
* Append exclude lookup paths to metadata driver.
73+
* Append exclude lookup paths to a metadata driver.
7174
*
7275
* @param string[] $paths
7376
*/
@@ -120,13 +123,17 @@ public function getAllClassNames(): array
120123
return $this->classNames;
121124
}
122125

123-
if ($this->paths === []) {
126+
if ($this->paths === [] && ! isset($this->filePaths)) {
124127
throw MappingException::pathRequiredForDriver(static::class);
125128
}
126129

127130
$dirFilesIterator = new DirectoryFilesIterator($this->paths, $this->fileExtension);
131+
128132
/** @var iterable<string> $filePathsIterator */
129-
$filePathsIterator = new FilePathNameIterator($dirFilesIterator);
133+
$filePathsIterator = $this->concatIterables(
134+
$this->filePaths ?? [],
135+
new FilePathNameIterator($dirFilesIterator),
136+
);
130137

131138
/** @var array<string,true> $includedFiles */
132139
$includedFiles = [];
@@ -172,4 +179,19 @@ public function getAllClassNames(): array
172179

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

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
@@ -12,14 +12,16 @@
1212
use Doctrine\Tests\Persistence\Mapping\_files\colocated\TestClass;
1313
use Generator;
1414
use PHPUnit\Framework\TestCase;
15+
use Traversable;
1516

17+
use function is_file;
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($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 iterable<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 || is_file($paths[0]);
127+
128+
if (! $isFilePaths) {
129+
$this->paths = $paths;
130+
} else {
131+
$this->filePaths = $paths;
132+
}
91133
}
92134

93135
/**

0 commit comments

Comments
 (0)