Skip to content

Commit

Permalink
Merge branch 'feature/#214-directory-source-locator'
Browse files Browse the repository at this point in the history
Close #214
  • Loading branch information
Ocramius committed Sep 26, 2016
2 parents 635e698 + 9a40fa2 commit 5d65651
Show file tree
Hide file tree
Showing 19 changed files with 548 additions and 0 deletions.
39 changes: 39 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ within the `Reflector`s. The library comes bundled with the following
* `AggregateSourceLocator` - a combination of multiple `SourceLocator`s which
are hunted through in the given order to locate the source.

* `FileIteratorSourceLocator` - iterates all files in a given iterator
containing `SplFileInfo` instances.

* `DirectoriesSourceLocator` - iterates over all `.php` files in a list of
directories, and all their descendants.

A `SourceLocator` is a callable, which when invoked must be given an
`Identifier` (which describes a class/function/etc.). The `SourceLocator`
should be written so that it returns a `Reflection` object directly.
Expand Down Expand Up @@ -172,6 +178,39 @@ $reflector = new ClassReflector(new SingleFileSourceLocator('path/to/file.php'))
$classes = $reflector->getAllClasses();
```

### Fetch reflections of all the classes in a directory

```php
<?php

$singleDirectorySourceLocator = new SingleDirectorySourceLocator(new \FilesystemIterator('path/to/directory'));
$reflector = new ClassReflector($singleDirectorySourceLocator);
$classes = $reflector->getAllClasses();
```

### Fetch reflections of all the classes in a directory (including sub directories)

```php
<?php

$rdi = new \RecursiveDirectoryIterator('path/to/directory');
$singleDirectorySourceLocator = new SingleDirectorySourceLocator(new \RecursiveIteratorIterator($rdi));
$reflector = new ClassReflector($singleDirectorySourceLocator);
$classes = $reflector->getAllClasses();
```

### Fetch reflections of all the classes in multiple directories (including sub directories)

```php
<?php

$directoriesToScan = ['path/to/directory1', 'path/to/directory2'];
$multipleDirectoriesSourceLocator = new MultipleDirectoriesAggregateSourceLocator($directoriesToScan);
$reflector = new ClassReflector($multipleDirectoriesSourceLocator);
$classes = $reflector->getAllClasses();
```


## Reflecting Functions

The `FunctionReflector` is used to create Better Reflection `ReflectionFunction`
Expand Down
33 changes: 33 additions & 0 deletions src/SourceLocator/Exception/InvalidDirectory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace BetterReflection\SourceLocator\Exception;

class InvalidDirectory extends \RuntimeException
{
/**
* @param string $nonDirectory
*
* @return InvalidDirectory
*/
public static function fromNonDirectory($nonDirectory)
{
if (! file_exists($nonDirectory)) {
return new self(sprintf('"%s" does not exists', $nonDirectory));
}

return new self(sprintf('"%s" must be a directory, not a file', $nonDirectory));
}

/**
* @param mixed $nonStringValue
*
* @return InvalidDirectory
*/
public static function fromNonStringValue($nonStringValue)
{
return new self(sprintf(
'Expected string, %s given',
is_object($nonStringValue) ? get_class($nonStringValue) : gettype($nonStringValue)
));
}
}
19 changes: 19 additions & 0 deletions src/SourceLocator/Exception/InvalidFileInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace BetterReflection\SourceLocator\Exception;

class InvalidFileInfo extends \RuntimeException
{
/**
* @param mixed $nonSplFileInfo
*
* @return InvalidFileInfo
*/
public static function fromNonSplFileInfo($nonSplFileInfo)
{
return new self(sprintf(
'Expected an iterator of SplFileInfo instances, %s given instead',
is_object($nonSplFileInfo) ? get_class($nonSplFileInfo) : gettype($nonSplFileInfo)
));
}
}
65 changes: 65 additions & 0 deletions src/SourceLocator/Type/DirectoriesSourceLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace BetterReflection\SourceLocator\Type;

use BetterReflection\Identifier\Identifier;
use BetterReflection\Identifier\IdentifierType;
use BetterReflection\Reflector\Reflector;
use BetterReflection\SourceLocator\Exception\InvalidDirectory;
use BetterReflection\SourceLocator\Exception\InvalidFileInfo;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

/**
* This source locator loads all php files in an entire directory or multiple directories.
*/
class DirectoriesSourceLocator implements SourceLocator
{
/**
* @var AggregateSourceLocator
*/
private $aggregateSourceLocator;

/**
* @param string[] $directories directories to scan
*
* @throws InvalidDirectory
* @throws InvalidFileInfo
*/
public function __construct(array $directories)
{
$this->aggregateSourceLocator = new AggregateSourceLocator(array_values(array_map(
function ($directory) {
if (! is_string($directory)) {
throw InvalidDirectory::fromNonStringValue($directory);
}

if (! is_dir($directory)) {
throw InvalidDirectory::fromNonDirectory($directory);
}

return new FileIteratorSourceLocator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
$directory,
RecursiveDirectoryIterator::SKIP_DOTS
)));
},
$directories
)));
}

/**
* {@inheritDoc}
*/
public function locateIdentifier(Reflector $reflector, Identifier $identifier)
{
return $this->aggregateSourceLocator->locateIdentifier($reflector, $identifier);
}

/**
* {@inheritDoc}
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType)
{
return $this->aggregateSourceLocator->locateIdentifiersByType($reflector, $identifierType);
}
}
74 changes: 74 additions & 0 deletions src/SourceLocator/Type/FileIteratorSourceLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace BetterReflection\SourceLocator\Type;

use BetterReflection\Identifier\Identifier;
use BetterReflection\Identifier\IdentifierType;
use BetterReflection\Reflector\Reflector;
use BetterReflection\SourceLocator\Exception\InvalidFileInfo;

/**
* This source locator loads all php files from \FileSystemIterator
*/
class FileIteratorSourceLocator implements SourceLocator
{
/**
* @var AggregateSourceLocator|null
*/
private $aggregateSourceLocator;

/**
* @var \Iterator|\SplFileInfo[]
*/
private $fileSystemIterator;

/**
* @param \Iterator|\SplFileInfo[] $fileInfoIterator note: only \SplFileInfo allowed in this iterator
*
* @throws InvalidFileInfo In case of iterator not contains only SplFileInfo
*/
public function __construct(\Iterator $fileInfoIterator)
{
foreach ($fileInfoIterator as $fileInfo) {
if (! $fileInfo instanceof \SplFileInfo) {
throw InvalidFileInfo::fromNonSplFileInfo($fileInfo);
}
}

$this->fileSystemIterator = $fileInfoIterator;
}

/**
* @return AggregateSourceLocator
*/
private function getAggregatedSourceLocator()
{
return $this->aggregateSourceLocator ?
$this->aggregateSourceLocator : new AggregateSourceLocator(array_values(array_filter(array_map(
function (\SplFileInfo $item) {
if (! ($item->isFile() && pathinfo($item->getRealPath(), \PATHINFO_EXTENSION) === 'php')) {
return null;
}

return new SingleFileSourceLocator($item->getRealPath());
},
iterator_to_array($this->fileSystemIterator)
))));
}

/**
* {@inheritDoc}
*/
public function locateIdentifier(Reflector $reflector, Identifier $identifier)
{
return $this->getAggregatedSourceLocator()->locateIdentifier($reflector, $identifier);
}

/**
* {@inheritDoc}
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType)
{
return $this->getAggregatedSourceLocator()->locateIdentifiersByType($reflector, $identifierType);
}
}
4 changes: 4 additions & 0 deletions test/unit/Assets/DirectoryScannerAssets/Bar/Empty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
/**
* test assets for DirectoryScanner
*/
7 changes: 7 additions & 0 deletions test/unit/Assets/DirectoryScannerAssets/Bar/FooBar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace BetterReflectionTest\Assets\DirectoryScannerAssets\Bar;

class FooBar
{
}
1 change: 1 addition & 0 deletions test/unit/Assets/DirectoryScannerAssets/Bar/FooBar.tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test assets for DirectoryScanner
7 changes: 7 additions & 0 deletions test/unit/Assets/DirectoryScannerAssets/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace BetterReflectionTest\Assets\DirectoryScannerAssets;

class Foo
{
}
1 change: 1 addition & 0 deletions test/unit/Assets/DirectoryScannerAssets/Foo.tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test assets for DirectoryScanner
4 changes: 4 additions & 0 deletions test/unit/Assets/DirectoryScannerAssetsFoo/Bar/Empty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
/**
* test assets for DirectoryScanner
*/
8 changes: 8 additions & 0 deletions test/unit/Assets/DirectoryScannerAssetsFoo/Bar/FooBar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace BetterReflectionTest\Assets\DirectoryScannerAssetsFoo\Bar;

class FooBar
{

}
1 change: 1 addition & 0 deletions test/unit/Assets/DirectoryScannerAssetsFoo/Bar/FooBar.tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test assets for DirectoryScanner
8 changes: 8 additions & 0 deletions test/unit/Assets/DirectoryScannerAssetsFoo/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace BetterReflectionTest\Assets\DirectoryScannerAssetsFoo;

class Foo
{

}
1 change: 1 addition & 0 deletions test/unit/Assets/DirectoryScannerAssetsFoo/Foo.tmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test assets for DirectoryScanner
59 changes: 59 additions & 0 deletions test/unit/SourceLocator/Exception/InvalidDirectoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace BetterReflectionTest\SourceLocator\Exception;

use BetterReflection\SourceLocator\Exception\InvalidDirectory;

/**
* @covers \BetterReflection\SourceLocator\Exception\InvalidDirectory
*/
class InvalidDirectoryTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider nonStringValuesProvider
*
* @param string $expectedMessage
* @param mixed $value
*
* @return void
*/
public function testFromNonStringValue($expectedMessage, $value)
{
$exception = InvalidDirectory::fromNonStringValue($value);

self::assertInstanceOf(InvalidDirectory::class, $exception);
self::assertSame($expectedMessage, $exception->getMessage());
}

/**
* @return string[][]|mixed[][]
*/
public function nonStringValuesProvider()
{
return [
['Expected string, stdClass given', new \stdClass()],
['Expected string, boolean given', true],
['Expected string, NULL given', null],
['Expected string, integer given', 100],
['Expected string, double given', 100.35],
['Expected string, array given', []],
];
}

public function testFromNonDirectoryWithNonExistingPath()
{
$directory = uniqid(sys_get_temp_dir() . 'non-existing', true);
$exception = InvalidDirectory::fromNonDirectory($directory);

self::assertInstanceOf(InvalidDirectory::class, $exception);
self::assertSame(sprintf('"%s" does not exists', $directory), $exception->getMessage());
}

public function testFromNonDirectoryWithFile()
{
$exception = InvalidDirectory::fromNonDirectory(__FILE__);

self::assertInstanceOf(InvalidDirectory::class, $exception);
self::assertSame(sprintf('"%s" must be a directory, not a file', __FILE__), $exception->getMessage());
}
}
Loading

0 comments on commit 5d65651

Please sign in to comment.