new Deps
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
/ci/ export-ignore
|
||||
/docs/ export-ignore
|
||||
/test/ export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.gitlab-ci.yml export-ignore
|
||||
/composer.lock export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2018 Hayden Pierce
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
ClassFinder
|
||||
===========
|
||||
|
||||
A dead simple utility to identify classes in a given namespace.
|
||||
|
||||
This package is an improved implementation of an [answer on Stack Overflow](https://stackoverflow.com/a/40229665/3000068)
|
||||
and provides additional features with less configuration required.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* Application is using Composer.
|
||||
* Classes can be autoloaded with Composer.
|
||||
* PHP >= 5.3.0
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Installing is done by requiring it with Composer.
|
||||
|
||||
```
|
||||
$ composer require haydenpierce/class-finder
|
||||
```
|
||||
|
||||
No other installation methods are currently supported.
|
||||
|
||||
Supported Autoloading Methods
|
||||
--------------------------------
|
||||
|
||||
| Method | Supported | with `ClassFinder::RECURSIVE_MODE` |
|
||||
| ---------- | --------- | ---------------------------------- |
|
||||
| PSR-4 | ✔️ | ✔️ |
|
||||
| PSR-0 | ❌️* | ❌️* |
|
||||
| Classmap | ✔️ | ✔️ |
|
||||
| Files | ✔️^ | ❌️** |
|
||||
|
||||
\^ Experimental.
|
||||
|
||||
\* Planned.
|
||||
|
||||
\** Not planned. Open an issue if you need this feature.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Standard Mode**
|
||||
|
||||
```
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
|
||||
|
||||
/**
|
||||
* array(
|
||||
* 'TestApp1\Foo\Bar',
|
||||
* 'TestApp1\Foo\Baz',
|
||||
* 'TestApp1\Foo\Foo'
|
||||
* )
|
||||
*/
|
||||
var_dump($classes);
|
||||
```
|
||||
|
||||
**Recursive Mode** *(in v0.3-beta)*
|
||||
|
||||
```
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE);
|
||||
|
||||
/**
|
||||
* array(
|
||||
* 'TestApp1\Foo\Bar',
|
||||
* 'TestApp1\Foo\Baz',
|
||||
* 'TestApp1\Foo\Foo',
|
||||
* 'TestApp1\Foo\Box\Bar',
|
||||
* 'TestApp1\Foo\Box\Baz',
|
||||
* 'TestApp1\Foo\Box\Foo',
|
||||
* 'TestApp1\Foo\Box\Lon\Bar',
|
||||
* 'TestApp1\Foo\Box\Lon\Baz',
|
||||
* 'TestApp1\Foo\Box\Lon\Foo',
|
||||
* )
|
||||
*/
|
||||
var_dump($classes);
|
||||
```
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
[Changelog](docs/changelog.md)
|
||||
|
||||
**Exceptions**:
|
||||
|
||||
* [Files could not locate PHP](docs/exceptions/filesCouldNotLocatePHP.md)
|
||||
* [Files exec not available](docs/exceptions/filesExecNotAvailable.md)
|
||||
* [Missing composer.json](docs/exceptions/missingComposerConfig.md)
|
||||
|
||||
**Internals**
|
||||
|
||||
* [How Testing Works](docs/testing.md)
|
||||
* [Continuous Integration Notes](docs/ci.md)
|
||||
|
||||
Future Work
|
||||
-----------
|
||||
|
||||
> **WARNING**: Before 1.0.0, expect that bug fixes _will not_ be backported to older versions. Backwards incompatible changes
|
||||
may be introduced in minor 0.X.Y versions, where X changes.
|
||||
|
||||
* `psr0` support
|
||||
|
||||
* Additional features:
|
||||
|
||||
Various ideas:
|
||||
|
||||
* ~~`ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE)`.
|
||||
Providing classes multiple namespaces deep.~~ (included v0.3-beta)
|
||||
|
||||
* `ClassFinder::getClassesImplementingInterface('TestApp1\Foo', 'TestApp1\FooInterface', ClassFinder::RECURSIVE_MODE)`.
|
||||
Filtering classes to only classes that implement a namespace.
|
||||
|
||||
* `ClassFinder::debugRenderReport('TestApp1\Foo\Baz')`
|
||||
Guidance for solving "class not found" errors resulting from typos in namespaces, missing directories, etc. Would print
|
||||
an HTML report. Not intended for production use, but debugging.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "haydenpierce/class-finder",
|
||||
"description" : "A library that can provide of a list of classes in a given namespace",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"version": "0.4.3",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Hayden Pierce",
|
||||
"email": "hayden@haydenpierce.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~9.0",
|
||||
"mikey179/vfsstream": "^1.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"HaydenPierce\\ClassFinder\\": "src/",
|
||||
"HaydenPierce\\ClassFinder\\UnitTest\\": "test/unit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder;
|
||||
|
||||
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
/** @var string */
|
||||
private $appRoot;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->appRoot = $this->findAppRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function findAppRoot()
|
||||
{
|
||||
if ($this->appRoot) {
|
||||
$appRoot = $this->appRoot;
|
||||
} else {
|
||||
$workingDirectory = str_replace('\\', '/', __DIR__);
|
||||
$workingDirectory = str_replace('/vendor/haydenpierce/class-finder/src', '', $workingDirectory);
|
||||
$directoryPathPieces = explode('/', $workingDirectory);
|
||||
|
||||
$appRoot = null;
|
||||
do {
|
||||
$path = implode('/', $directoryPathPieces) . '/composer.json';
|
||||
if (file_exists($path)) {
|
||||
$appRoot = implode('/', $directoryPathPieces) . '/';
|
||||
} else {
|
||||
array_pop($directoryPathPieces);
|
||||
}
|
||||
} while (is_null($appRoot) && count($directoryPathPieces) > 0);
|
||||
}
|
||||
|
||||
$this->throwIfInvalidAppRoot($appRoot);
|
||||
|
||||
$this->appRoot= $appRoot;
|
||||
return $this->appRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appRoot
|
||||
* @return void
|
||||
* @throws ClassFinderException
|
||||
*/
|
||||
private function throwIfInvalidAppRoot($appRoot)
|
||||
{
|
||||
if (!file_exists($appRoot . '/composer.json')) {
|
||||
throw new ClassFinderException(sprintf("Could not locate composer.json. You can get around this by setting ClassFinder::\$appRoot manually. See '%s' for details.",
|
||||
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/missingComposerConfig.md'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppRoot()
|
||||
{
|
||||
if ($this->appRoot === null) {
|
||||
$this->appRoot = $this->findAppRoot();
|
||||
}
|
||||
|
||||
$this->throwIfInvalidAppRoot($this->appRoot);
|
||||
|
||||
return $this->appRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appRoot
|
||||
* @return void
|
||||
*/
|
||||
public function setAppRoot($appRoot)
|
||||
{
|
||||
$this->appRoot = $appRoot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder;
|
||||
|
||||
use HaydenPierce\ClassFinder\Classmap\ClassmapEntryFactory;
|
||||
use HaydenPierce\ClassFinder\Classmap\ClassmapFinder;
|
||||
use HaydenPierce\ClassFinder\Files\FilesEntryFactory;
|
||||
use HaydenPierce\ClassFinder\Files\FilesFinder;
|
||||
use HaydenPierce\ClassFinder\PSR4\PSR4Finder;
|
||||
use HaydenPierce\ClassFinder\PSR4\PSR4NamespaceFactory;
|
||||
|
||||
class ClassFinder
|
||||
{
|
||||
const STANDARD_MODE = 1;
|
||||
const RECURSIVE_MODE = 2;
|
||||
|
||||
/** @var AppConfig */
|
||||
private static $config;
|
||||
|
||||
/** @var PSR4Finder */
|
||||
private static $psr4;
|
||||
|
||||
/** @var ClassmapFinder */
|
||||
private static $classmap;
|
||||
|
||||
/** @var FilesFinder */
|
||||
private static $files;
|
||||
|
||||
/** @var boolean */
|
||||
private static $useFilesSupport = false;
|
||||
|
||||
/** @var boolean */
|
||||
private static $usePSR4Support = true;
|
||||
|
||||
/** @var boolean */
|
||||
private static $useClassmapSupport = true;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initialize()
|
||||
{
|
||||
if (!(self::$config instanceof AppConfig)) {
|
||||
self::$config = new AppConfig();
|
||||
}
|
||||
|
||||
if (!(self::$psr4 instanceof PSR4Finder)) {
|
||||
$PSR4Factory = new PSR4NamespaceFactory(self::$config);
|
||||
self::$psr4 = new PSR4Finder($PSR4Factory);
|
||||
}
|
||||
|
||||
if (!(self::$classmap instanceof ClassmapFinder)) {
|
||||
$classmapFactory = new ClassmapEntryFactory(self::$config);
|
||||
self::$classmap = new ClassmapFinder($classmapFactory);
|
||||
}
|
||||
|
||||
if (!(self::$files instanceof FilesFinder) && self::$useFilesSupport) {
|
||||
$filesFactory = new FilesEntryFactory(self::$config);
|
||||
self::$files = new FilesFinder($filesFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify classes in a given namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getClassesInNamespace($namespace, $options = self::STANDARD_MODE)
|
||||
{
|
||||
self::initialize();
|
||||
|
||||
$findersWithNamespace = self::findersWithNamespace($namespace);
|
||||
|
||||
$classes = array_reduce($findersWithNamespace, function($carry, FinderInterface $finder) use ($namespace, $options){
|
||||
return array_merge($carry, $finder->findClasses($namespace, $options));
|
||||
}, array());
|
||||
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given namespace contains any classes.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public static function namespaceHasClasses($namespace)
|
||||
{
|
||||
self::initialize();
|
||||
|
||||
return count(self::findersWithNamespace($namespace)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appRoot
|
||||
* @return void
|
||||
*/
|
||||
public static function setAppRoot($appRoot)
|
||||
{
|
||||
self::initialize();
|
||||
self::$config->setAppRoot($appRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function enableExperimentalFilesSupport()
|
||||
{
|
||||
self::$useFilesSupport = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function disableExperimentalFilesSupport()
|
||||
{
|
||||
self::$useFilesSupport = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function enablePSR4Support()
|
||||
{
|
||||
self::$usePSR4Support = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function disablePSR4Support()
|
||||
{
|
||||
self::$usePSR4Support = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function enableClassmapSupport()
|
||||
{
|
||||
self::$useClassmapSupport = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function disableClassmapSupport()
|
||||
{
|
||||
self::$useClassmapSupport = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FinderInterface[]
|
||||
*/
|
||||
private static function getSupportedFinders()
|
||||
{
|
||||
$supportedFinders = array();
|
||||
|
||||
/*
|
||||
* This is done for testing. For some tests, allowing PSR4 classes contaminates the test results. This could also be
|
||||
* disabled for performance reasons (less finders in use means less work), but most people probably won't do that.
|
||||
*/
|
||||
if (self::$usePSR4Support) {
|
||||
$supportedFinders[] = self::$psr4;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is done for testing. For some tests, allowing classmap classes contaminates the test results. This could also be
|
||||
* disabled for performance reasons (less finders in use means less work), but most people probably won't do that.
|
||||
*/
|
||||
if (self::$useClassmapSupport) {
|
||||
$supportedFinders[] = self::$classmap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Files support is tucked away behind a flag because it will need to use some kind of shell access via exec, or
|
||||
* system.
|
||||
*
|
||||
* #1 Many environments (such as shared space hosts) may not allow these functions, and attempting to call
|
||||
* these functions will blow up.
|
||||
* #2 I've heard of performance issues with calling these functions.
|
||||
* #3 Files support probably doesn't benefit most projects.
|
||||
* #4 Using exec() or system() is against many PHP developers' religions.
|
||||
*/
|
||||
if (self::$useFilesSupport) {
|
||||
$supportedFinders[] = self::$files;
|
||||
}
|
||||
|
||||
return $supportedFinders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return FinderInterface[]
|
||||
*/
|
||||
private static function findersWithNamespace($namespace)
|
||||
{
|
||||
$findersWithNamespace = array_filter(self::getSupportedFinders(), function (FinderInterface $finder) use ($namespace) {
|
||||
return $finder->isNamespaceKnown($namespace);
|
||||
});
|
||||
|
||||
return $findersWithNamespace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Classmap;
|
||||
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
|
||||
class ClassmapEntry
|
||||
{
|
||||
/** @var string */
|
||||
private $className;
|
||||
|
||||
/**
|
||||
* @param string $fullyQualifiedClassName
|
||||
*/
|
||||
public function __construct($fullyQualifiedClassName)
|
||||
{
|
||||
$this->className = $fullyQualifiedClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function knowsNamespace($namespace)
|
||||
{
|
||||
return strpos($this->className, $namespace) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function matches($namespace, $options)
|
||||
{
|
||||
if ($options === ClassFinder::RECURSIVE_MODE) {
|
||||
return $this->doesMatchAnyNamespace($namespace);
|
||||
} else {
|
||||
return $this->doesMatchDirectNamespace($namespace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClassName()
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the class is a child or subchild of the given namespace.
|
||||
*
|
||||
* @param $namespace
|
||||
* @return bool
|
||||
*/
|
||||
private function doesMatchAnyNamespace($namespace)
|
||||
{
|
||||
return strpos($this->getClassName(),$namespace) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the class is a DIRECT child of the given namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
private function doesMatchDirectNamespace($namespace)
|
||||
{
|
||||
$classNameFragments = explode('\\', $this->getClassName());
|
||||
array_pop($classNameFragments);
|
||||
$classNamespace = implode('\\', $classNameFragments);
|
||||
|
||||
$namespace = trim($namespace, '\\');
|
||||
|
||||
return $namespace === $classNamespace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Classmap;
|
||||
|
||||
use HaydenPierce\ClassFinder\AppConfig;
|
||||
|
||||
class ClassmapEntryFactory
|
||||
{
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
|
||||
public function __construct(AppConfig $appConfig)
|
||||
{
|
||||
$this->appConfig = $appConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassmapEntry[]
|
||||
*/
|
||||
public function getClassmapEntries()
|
||||
{
|
||||
// Composer will compile user declared mappings to autoload_classmap.php. So no additional work is needed
|
||||
// to fetch user provided entries.
|
||||
$classmap = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_classmap.php');
|
||||
|
||||
// if classmap has no entries return empty array
|
||||
if(count($classmap) == 0) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$classmapKeys = array_keys($classmap);
|
||||
return array_map(function($index) use ($classmapKeys){
|
||||
return new ClassmapEntry($classmapKeys[$index]);
|
||||
}, range(0, count($classmap) - 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Classmap;
|
||||
|
||||
use HaydenPierce\ClassFinder\FinderInterface;
|
||||
|
||||
class ClassmapFinder implements FinderInterface
|
||||
{
|
||||
/** @var ClassmapEntryFactory */
|
||||
private $factory;
|
||||
|
||||
public function __construct(ClassmapEntryFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isNamespaceKnown($namespace)
|
||||
{
|
||||
$classmapEntries = $this->factory->getClassmapEntries();
|
||||
|
||||
foreach($classmapEntries as $classmapEntry) {
|
||||
if ($classmapEntry->knowsNamespace($namespace)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*/
|
||||
public function findClasses($namespace, $options)
|
||||
{
|
||||
$classmapEntries = $this->factory->getClassmapEntries();
|
||||
|
||||
$matchingEntries = array_filter($classmapEntries, function(ClassmapEntry $entry) use ($namespace, $options) {
|
||||
return $entry->matches($namespace, $options);
|
||||
});
|
||||
|
||||
return array_map(function(ClassmapEntry $entry) {
|
||||
return $entry->getClassName();
|
||||
}, $matchingEntries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Exception;
|
||||
|
||||
class ClassFinderException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Files;
|
||||
|
||||
class FilesEntry
|
||||
{
|
||||
/** @var string */
|
||||
private $file;
|
||||
|
||||
/** @var string */
|
||||
private $php;
|
||||
|
||||
/**
|
||||
* @param string $fileToInclude
|
||||
* @param string $php
|
||||
*/
|
||||
public function __construct($fileToInclude, $php)
|
||||
{
|
||||
$this->file = $this->normalizePath($fileToInclude);
|
||||
$this->php = $php;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function knowsNamespace($namespace)
|
||||
{
|
||||
$classes = $this->getClassesInFile();
|
||||
|
||||
foreach($classes as $class) {
|
||||
if (strpos($class, $namespace) !== false) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of classes that belong to the given namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return string[]
|
||||
*/
|
||||
public function getClasses($namespace)
|
||||
{
|
||||
$classes = $this->getClassesInFile();
|
||||
|
||||
return array_values(array_filter($classes, function($class) use ($namespace) {
|
||||
$classNameFragments = explode('\\', $class);
|
||||
array_pop($classNameFragments);
|
||||
$classNamespace = implode('\\', $classNameFragments);
|
||||
|
||||
$namespace = trim($namespace, '\\');
|
||||
|
||||
return $namespace === $classNamespace;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically execute files and check for defined classes.
|
||||
*
|
||||
* This is where the real magic happens. Since classes in a randomly included file could contain classes in any namespace,
|
||||
* (or even multiple namespaces!) we must execute the file and check for newly defined classes. This has a potential
|
||||
* downside that files being executed will execute their side effects - which may be undesirable. However, Composer
|
||||
* will require these files anyway - so hopefully causing those side effects isn't that big of a deal.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getClassesInFile()
|
||||
{
|
||||
// get_declared_classes() returns a bunch of classes that are built into PHP. So we need a control here.
|
||||
$script = "var_export(get_declared_classes());";
|
||||
exec($this->php . " -r \"$script\"", $output);
|
||||
$classes = 'return ' . implode('', $output) . ';';
|
||||
$initialClasses = eval($classes);
|
||||
|
||||
// clear the exec() buffer.
|
||||
unset($output);
|
||||
|
||||
// This brings in the new classes. so $classes here will include the PHP defaults and the newly defined classes
|
||||
$script = "require_once '{$this->file}'; var_export(get_declared_classes());";
|
||||
exec($this->php . ' -r "' . $script . '"', $output);
|
||||
$classes = 'return ' . implode('', $output) . ';';
|
||||
$allClasses = eval($classes);
|
||||
|
||||
return array_diff($allClasses, $initialClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Similar to PSR4Namespace::normalizePath. Maybe we refactor?
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function normalizePath($path)
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Files;
|
||||
|
||||
use HaydenPierce\ClassFinder\AppConfig;
|
||||
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
|
||||
|
||||
class FilesEntryFactory
|
||||
{
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
|
||||
public function __construct(AppConfig $appConfig)
|
||||
{
|
||||
$this->appConfig = $appConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FilesEntry[]
|
||||
*/
|
||||
public function getFilesEntries()
|
||||
{
|
||||
$files = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_files.php');
|
||||
$phpPath = $this->findPHP();
|
||||
|
||||
$filesKeys = array_values($files);
|
||||
return array_map(function($index) use ($filesKeys, $phpPath){
|
||||
return new FilesEntry($filesKeys[$index], $phpPath);
|
||||
}, range(0, count($files) - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the PHP interrupter.
|
||||
*
|
||||
* If PHP 5.4 or newer is used, the PHP_BINARY value is used.
|
||||
* Otherwise we attempt to find it from shell commands.
|
||||
*
|
||||
* @return string
|
||||
* @throws ClassFinderException
|
||||
*/
|
||||
private function findPHP()
|
||||
{
|
||||
if (defined("PHP_BINARY")) {
|
||||
// PHP_BINARY was made available in PHP 5.4
|
||||
$php = PHP_BINARY;
|
||||
} else {
|
||||
$isHostWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
if ($isHostWindows) {
|
||||
exec('where php', $output);
|
||||
$php = $output[0];
|
||||
} else {
|
||||
exec('which php', $output);
|
||||
$php = $output[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($php)) {
|
||||
throw new ClassFinderException(sprintf(
|
||||
'Could not locate PHP interrupter. See "%s" for details.',
|
||||
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/filesCouldNotLocatePHP.md'
|
||||
));
|
||||
}
|
||||
|
||||
return $php;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Files;
|
||||
|
||||
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
|
||||
use HaydenPierce\ClassFinder\FinderInterface;
|
||||
|
||||
class FilesFinder implements FinderInterface
|
||||
{
|
||||
/** @var FilesEntryFactory */
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @param FilesEntryFactory $factory
|
||||
* @throws ClassFinderException
|
||||
*/
|
||||
public function __construct(FilesEntryFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
|
||||
if (!function_exists('exec')) {
|
||||
throw new ClassFinderException(sprintf(
|
||||
'FilesFinder requires that exec() is available. Check your php.ini to see if it is disabled. See "%s" for details.',
|
||||
'https://gitlab.com/hpierce1102/ClassFinder/blob/master/docs/exceptions/filesExecNotAvailable.md'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isNamespaceKnown($namespace)
|
||||
{
|
||||
$filesEntries = $this->factory->getFilesEntries();
|
||||
|
||||
foreach($filesEntries as $filesEntry) {
|
||||
if ($filesEntry->knowsNamespace($namespace)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*/
|
||||
public function findClasses($namespace, $options)
|
||||
{
|
||||
$filesEntries = $this->factory->getFilesEntries();
|
||||
|
||||
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace){
|
||||
return array_merge($carry, $entry->getClasses($namespace));
|
||||
}, array());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder;
|
||||
|
||||
interface FinderInterface
|
||||
{
|
||||
/**
|
||||
* Find classes in a given namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*/
|
||||
public function findClasses($namespace, $options);
|
||||
|
||||
/**
|
||||
* Check if a given namespace is known.
|
||||
*
|
||||
* A namespace is "known" if a Finder can determine that the autoloader can create classes from that namespace.
|
||||
*
|
||||
* For instance:
|
||||
* If given a classmap for "TestApp1\Foo\Bar\Baz", the namespace "TestApp1\Foo" is known, even if nothing loads
|
||||
* from that namespace directly. It is known because classes that include that namespace are known.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isNamespaceKnown($namespace);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\PSR4;
|
||||
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
use HaydenPierce\ClassFinder\FinderInterface;
|
||||
|
||||
class PSR4Finder implements FinderInterface
|
||||
{
|
||||
/** @var PSR4NamespaceFactory */
|
||||
private $factory;
|
||||
|
||||
public function __construct(PSR4NamespaceFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*/
|
||||
public function findClasses($namespace, $options)
|
||||
{
|
||||
if ($options === ClassFinder::RECURSIVE_MODE) {
|
||||
$applicableNamespaces = $this->findAllApplicableNamespaces($namespace);
|
||||
}
|
||||
|
||||
if (empty($applicableNamespaces)) {
|
||||
$bestNamespace = $this->findBestPSR4Namespace($namespace);
|
||||
$applicableNamespaces = array($bestNamespace);
|
||||
}
|
||||
|
||||
return array_reduce($applicableNamespaces, function($carry, $psr4NamespaceOrNull) use ($namespace, $options) {
|
||||
if ($psr4NamespaceOrNull instanceof PSR4Namespace) {
|
||||
$classes = $psr4NamespaceOrNull->findClasses($namespace, $options);
|
||||
} else {
|
||||
$classes = array();
|
||||
}
|
||||
|
||||
return array_merge($carry, $classes);
|
||||
}, array());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isNamespaceKnown($namespace)
|
||||
{
|
||||
$composerNamespaces = $this->factory->getPSR4Namespaces();
|
||||
|
||||
foreach($composerNamespaces as $psr4Namespace) {
|
||||
if ($psr4Namespace->knowsNamespace($namespace)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return PSR4Namespace[]
|
||||
*/
|
||||
private function findAllApplicableNamespaces($namespace)
|
||||
{
|
||||
$composerNamespaces = $this->factory->getPSR4Namespaces();
|
||||
|
||||
return array_filter($composerNamespaces, function(PSR4Namespace $potentialNamespace) use ($namespace){
|
||||
return $potentialNamespace->isAcceptableNamespaceRecursiveMode($namespace);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return PSR4Namespace
|
||||
*/
|
||||
private function findBestPSR4Namespace($namespace)
|
||||
{
|
||||
$composerNamespaces = $this->factory->getPSR4Namespaces();
|
||||
|
||||
$acceptableNamespaces = array_filter($composerNamespaces, function(PSR4Namespace $potentialNamespace) use ($namespace){
|
||||
return $potentialNamespace->isAcceptableNamespace($namespace);
|
||||
});
|
||||
|
||||
$carry = new \stdClass();
|
||||
$carry->highestMatchingSegments = 0;
|
||||
$carry->bestNamespace = null;
|
||||
|
||||
/** @var PSR4Namespace $bestNamespace */
|
||||
$bestNamespace = array_reduce($acceptableNamespaces, function ($carry, PSR4Namespace $potentialNamespace) use ($namespace) {
|
||||
$matchingSegments = $potentialNamespace->countMatchingNamespaceSegments($namespace);
|
||||
|
||||
if ($matchingSegments > $carry->highestMatchingSegments) {
|
||||
$carry->highestMatchingSegments = $matchingSegments;
|
||||
$carry->bestNamespace = $potentialNamespace;
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, $carry);
|
||||
|
||||
return $bestNamespace->bestNamespace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\PSR4;
|
||||
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
|
||||
|
||||
class PSR4Namespace
|
||||
{
|
||||
/** @var string */
|
||||
private $namespace;
|
||||
|
||||
/** @var string[] */
|
||||
private $directories;
|
||||
|
||||
/** @var PSR4Namespace[] */
|
||||
private $directSubnamespaces;
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param string[] $directories
|
||||
*/
|
||||
public function __construct($namespace, $directories)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->directories = $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function knowsNamespace($namespace)
|
||||
{
|
||||
$numberOfSegments = count(explode('\\', $namespace));
|
||||
$matchingSegments = $this->countMatchingNamespaceSegments($namespace);
|
||||
|
||||
if ($matchingSegments === 0) {
|
||||
// Provided namespace doesn't map to anything registered.
|
||||
return false;
|
||||
} elseif ($numberOfSegments <= $matchingSegments) {
|
||||
// This namespace is a superset of the provided namespace. Namespace is known.
|
||||
return true;
|
||||
} else {
|
||||
// This namespace is a subset of the provided namespace. We must resolve the remaining segments to a directory.
|
||||
$relativePath = substr($namespace, strlen($this->namespace));
|
||||
foreach ($this->directories as $directory) {
|
||||
$path = $this->normalizePath($directory, $relativePath);
|
||||
if (is_dir($path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how many namespace segments match the internal namespace. This is useful because multiple namespaces
|
||||
* may technically match a registered namespace root, but one of the matches may be a better match. Namespaces that
|
||||
* match, but are not _the best_ match are incorrect matches. TestApp1\\ is **not** the best match when searching for
|
||||
* namespace TestApp1\\Multi\\Foo if TestApp1\\Multi was explicitly registered.
|
||||
*
|
||||
* PSR4Namespace $a;
|
||||
* $a->namespace = "TestApp1\\";
|
||||
* $a->countMatchingNamespaceSegments("TestApp1\\Multi") -> 1, TestApp1 matches.
|
||||
*
|
||||
* PSR4Namespace $b;
|
||||
* $b->namespace = "TestApp1\\Multi";
|
||||
* $b->countMatchingNamespaceSegments("TestApp1\\Multi") -> 2, TestApp1\\Multi matches
|
||||
*
|
||||
* PSR4Namespace $c;
|
||||
* $c->namespace = "HaydenPierce\\Foo\\Bar";
|
||||
* $c->countMatchingNamespaceSegments("TestApp1\\Multi") -> 0, No matches.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return int
|
||||
*/
|
||||
public function countMatchingNamespaceSegments($namespace)
|
||||
{
|
||||
$namespaceFragments = explode('\\', $namespace);
|
||||
$undefinedNamespaceFragments = array();
|
||||
|
||||
while($namespaceFragments) {
|
||||
$possibleNamespace = implode('\\', $namespaceFragments) . '\\';
|
||||
|
||||
if(strpos($this->namespace, $possibleNamespace) !== false){
|
||||
return count(explode('\\', $possibleNamespace)) - 1;
|
||||
}
|
||||
|
||||
array_unshift($undefinedNamespaceFragments, array_pop($namespaceFragments));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isAcceptableNamespace($namespace)
|
||||
{
|
||||
$namespaceSegments = count(explode('\\', $this->namespace)) - 1;
|
||||
$matchingSegments = $this->countMatchingNamespaceSegments($namespace);
|
||||
return $namespaceSegments === $matchingSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return bool
|
||||
*/
|
||||
public function isAcceptableNamespaceRecursiveMode($namespace)
|
||||
{
|
||||
// Remove prefix backslash (TODO: review if we do this eariler).
|
||||
$namespace = ltrim($namespace, '\\');
|
||||
|
||||
return strpos($this->namespace, $namespace) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to identify subnamespaces.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function findDirectories()
|
||||
{
|
||||
$self = $this;
|
||||
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
|
||||
$path = $self->normalizePath($directory, '');
|
||||
$realDirectory = realpath($path);
|
||||
if ($realDirectory !== false) {
|
||||
return array_merge($carry, array($realDirectory));
|
||||
} else {
|
||||
return $carry;
|
||||
}
|
||||
}, array());
|
||||
|
||||
$arraysOfClasses = array_map(function($directory) use ($self) {
|
||||
$files = scandir($directory);
|
||||
return array_map(function($file) use ($directory, $self) {
|
||||
return $self->normalizePath($directory, $file);
|
||||
}, $files);
|
||||
}, $directories);
|
||||
|
||||
$potentialDirectories = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
|
||||
return array_merge($carry, $arrayOfClasses);
|
||||
}, array());
|
||||
|
||||
// Remove '.' and '..' directories
|
||||
$potentialDirectories = array_filter($potentialDirectories, function($potentialDirectory) {
|
||||
$segments = explode('/', $potentialDirectory);
|
||||
$lastSegment = array_pop($segments);
|
||||
|
||||
return $lastSegment !== '.' && $lastSegment !== '..';
|
||||
});
|
||||
|
||||
$confirmedDirectories = array_filter($potentialDirectories, function($potentialDirectory) {
|
||||
return is_dir($potentialDirectory);
|
||||
});
|
||||
|
||||
return $confirmedDirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param int $options
|
||||
* @return string[]
|
||||
*/
|
||||
public function findClasses($namespace, $options = ClassFinder::STANDARD_MODE)
|
||||
{
|
||||
$relativePath = substr($namespace, strlen($this->namespace));
|
||||
|
||||
$self = $this;
|
||||
$directories = array_reduce($this->directories, function($carry, $directory) use ($relativePath, $namespace, $self){
|
||||
$path = $self->normalizePath($directory, $relativePath);
|
||||
$realDirectory = realpath($path);
|
||||
if ($realDirectory !== false) {
|
||||
return array_merge($carry, array($realDirectory));
|
||||
} else {
|
||||
return $carry;
|
||||
}
|
||||
}, array());
|
||||
|
||||
$arraysOfClasses = array_map(function($directory) {
|
||||
return scandir($directory);
|
||||
}, $directories);
|
||||
|
||||
$potentialClassFiles = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
|
||||
return array_merge($carry, $arrayOfClasses);
|
||||
}, array());
|
||||
|
||||
$potentialClasses = array_map(function($file) use ($namespace){
|
||||
return $namespace . '\\' . str_replace('.php', '', $file);
|
||||
}, $potentialClassFiles);
|
||||
|
||||
if ($options == ClassFinder::RECURSIVE_MODE) {
|
||||
return $this->getClassesFromListRecursively($namespace);
|
||||
} else {
|
||||
return array_filter($potentialClasses, function($potentialClass) {
|
||||
if (function_exists($potentialClass)) {
|
||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||
// Example: DeepCopy\deep_copy
|
||||
return false;
|
||||
} else {
|
||||
return class_exists($potentialClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDirectClassesOnly()
|
||||
{
|
||||
$self = $this;
|
||||
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
|
||||
$path = $self->normalizePath($directory, '');
|
||||
$realDirectory = realpath($path);
|
||||
if ($realDirectory !== false) {
|
||||
return array_merge($carry, array($realDirectory));
|
||||
} else {
|
||||
return $carry;
|
||||
}
|
||||
}, array());
|
||||
|
||||
$arraysOfClasses = array_map(function($directory) {
|
||||
return scandir($directory);
|
||||
}, $directories);
|
||||
|
||||
$potentialClassFiles = array_reduce($arraysOfClasses, function($carry, $arrayOfClasses) {
|
||||
return array_merge($carry, $arrayOfClasses);
|
||||
}, array());
|
||||
|
||||
$selfNamespace = $this->namespace; // PHP 5.3 BC
|
||||
$potentialClasses = array_map(function($file) use ($self, $selfNamespace) {
|
||||
return $selfNamespace . str_replace('.php', '', $file);
|
||||
}, $potentialClassFiles);
|
||||
|
||||
return array_filter($potentialClasses, function($potentialClass) {
|
||||
if (function_exists($potentialClass)) {
|
||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||
// Example: DeepCopy\deep_copy
|
||||
return false;
|
||||
} else {
|
||||
return class_exists($potentialClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @return string[]
|
||||
*/
|
||||
public function getClassesFromListRecursively($namespace)
|
||||
{
|
||||
$initialClasses = strpos( $this->namespace, $namespace) !== false ? $this->getDirectClassesOnly() : array();
|
||||
|
||||
return array_reduce($this->getDirectSubnamespaces(), function($carry, PSR4Namespace $subNamespace) use ($namespace) {
|
||||
return array_merge($carry, $subNamespace->getClassesFromListRecursively($namespace));
|
||||
}, $initialClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an absolute path and a relative path in a platform agnostic way.
|
||||
*
|
||||
* This method is also extracted so that it can be turned into a vfs:// stream URL for unit testing.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $relativePath
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalizePath($directory, $relativePath)
|
||||
{
|
||||
$path = str_replace('\\', '/', $directory . '/' . $relativePath);
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PSR4Namespace[]
|
||||
*/
|
||||
public function getDirectSubnamespaces()
|
||||
{
|
||||
return $this->directSubnamespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PSR4Namespace[] $directSubnamespaces
|
||||
*/
|
||||
public function setDirectSubnamespaces($directSubnamespaces)
|
||||
{
|
||||
$this->directSubnamespaces = $directSubnamespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
return trim($this->namespace, '\\');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace HaydenPierce\ClassFinder\PSR4;
|
||||
|
||||
use HaydenPierce\ClassFinder\AppConfig;
|
||||
use HaydenPierce\ClassFinder\Exception\ClassFinderException;
|
||||
|
||||
class PSR4NamespaceFactory
|
||||
{
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
|
||||
public function __construct(AppConfig $appConfig)
|
||||
{
|
||||
$this->appConfig = $appConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPSR4Namespaces()
|
||||
{
|
||||
$namespaces = $this->getUserDefinedPSR4Namespaces();
|
||||
$vendorNamespaces = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_psr4.php');
|
||||
|
||||
$namespaces = array_merge($vendorNamespaces, $namespaces);
|
||||
|
||||
// There's some wackiness going on here for PHP 5.3 compatibility.
|
||||
$names = array_keys($namespaces);
|
||||
$directories = array_values($namespaces);
|
||||
$self = $this;
|
||||
$namespaces = array_map(function($index) use ($self, $names, $directories) {
|
||||
return $self->createNamespace($names[$index], $directories[$index]);
|
||||
},range(0, count($namespaces) - 1));
|
||||
|
||||
return $namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getUserDefinedPSR4Namespaces()
|
||||
{
|
||||
$appRoot = $this->appConfig->getAppRoot();
|
||||
|
||||
$composerJsonPath = $appRoot . 'composer.json';
|
||||
$composerConfig = json_decode(file_get_contents($composerJsonPath));
|
||||
|
||||
if (!isset($composerConfig->autoload)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
//Apparently PHP doesn't like hyphens, so we use variable variables instead.
|
||||
$psr4 = "psr-4";
|
||||
return (array)$composerConfig->autoload->$psr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a namespace from composer_psr4.php and composer.json autoload.psr4 items.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param string[] $directories
|
||||
* @return PSR4Namespace
|
||||
* @throws ClassFinderException
|
||||
*/
|
||||
public function createNamespace($namespace, $directories)
|
||||
{
|
||||
if (is_string($directories)) {
|
||||
// This is an acceptable format according to composer.json
|
||||
$directories = array($directories);
|
||||
} elseif (is_array($directories)) {
|
||||
// composer_psr4.php seems to put everything in this format
|
||||
} else {
|
||||
throw new ClassFinderException('Unknown PSR4 definition.');
|
||||
}
|
||||
|
||||
$self = $this;
|
||||
$appConfig = $this->appConfig;
|
||||
$directories = array_map(function($directory) use ($self, $appConfig) {
|
||||
if ($self->isAbsolutePath($directory)) {
|
||||
return $directory;
|
||||
} else {
|
||||
return $appConfig->getAppRoot() . $directory;
|
||||
}
|
||||
}, $directories);
|
||||
|
||||
$directories = array_filter(array_map(function($directory) {
|
||||
return realpath($directory);
|
||||
}, $directories));
|
||||
|
||||
$psr4Namespace = new PSR4Namespace($namespace, $directories);
|
||||
|
||||
$subNamespaces = $this->getSubnamespaces($psr4Namespace);
|
||||
$psr4Namespace->setDirectSubnamespaces($subNamespaces);
|
||||
|
||||
return $psr4Namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PSR4Namespace $psr4Namespace
|
||||
* @return PSR4Namespace[]
|
||||
*/
|
||||
private function getSubnamespaces(PSR4Namespace $psr4Namespace)
|
||||
{
|
||||
// Scan it's own directories.
|
||||
$directories = $psr4Namespace->findDirectories();
|
||||
|
||||
$self = $this;
|
||||
$subnamespaces = array_map(function($directory) use ($self, $psr4Namespace){
|
||||
$segments = explode('/', $directory);
|
||||
$subnamespaceSegment = array_pop($segments);
|
||||
|
||||
$namespace = $psr4Namespace->getNamespace() . "\\" . $subnamespaceSegment . "\\";
|
||||
return $self->createNamespace($namespace, $directory);
|
||||
}, $directories);
|
||||
|
||||
return $subnamespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is absolute.
|
||||
*
|
||||
* Mostly this answer https://stackoverflow.com/a/38022806/3000068
|
||||
* A few changes: Changed exceptions to be ClassFinderExceptions, removed some ctype dependencies,
|
||||
* updated the root prefix regex to handle Window paths better.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
* @throws ClassFinderException
|
||||
*/
|
||||
public function isAbsolutePath($path) {
|
||||
if (!is_string($path)) {
|
||||
$mess = sprintf('String expected but was given %s', gettype($path));
|
||||
throw new ClassFinderException($mess);
|
||||
}
|
||||
|
||||
// Optional wrapper(s).
|
||||
$regExp = '%^(?<wrappers>(?:[[:print:]]{2,}://)*)';
|
||||
// Optional root prefix.
|
||||
$regExp .= '(?<root>(?:[[:alpha:]]:[/\\\\]|/)?)';
|
||||
// Actual path.
|
||||
$regExp .= '(?<path>(?:[[:print:]]*))$%';
|
||||
$parts = array();
|
||||
if (!preg_match($regExp, $path, $parts)) {
|
||||
$mess = sprintf('Path is NOT valid, was given %s', $path);
|
||||
throw new ClassFinderException($mess);
|
||||
}
|
||||
if ('' !== $parts['root']) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user