Skip to content

Commit

Permalink
Refactor to reduce complexity
Browse files Browse the repository at this point in the history
  • Loading branch information
Riimu committed Jan 8, 2015
1 parent da49d2f commit 5ace999
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 95 deletions.
103 changes: 8 additions & 95 deletions src/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ class ClassLoader
/** @var array List of PSR-0 compatible paths by namespace */
private $basePaths;

/** @var string[] List of file extensions used to find files */
private $fileExtensions;

/** @var boolean Whether to look for classes in include_path or not */
private $useIncludePath;

/** @var callable The autoload method used to load classes */
private $loader;

/** @var \Riimu\Kit\ClassLoader\FileFinder Finder used to find class files */
private $finder;

/** @var boolean Whether loadClass should return values and throw exceptions or not */
protected $verbose;

Expand All @@ -54,10 +54,10 @@ public function __construct()
{
$this->prefixPaths = [];
$this->basePaths = [];
$this->fileExtensions = ['.php'];
$this->useIncludePath = false;
$this->verbose = true;
$this->loader = [$this, 'loadClass'];
$this->finder = new FileFinder();
}

/**
Expand Down Expand Up @@ -122,14 +122,15 @@ public function setVerbose($enabled)
/**
* Sets list of dot included file extensions to use for finding files.
*
* Defaults to ['.php']
* If no list of extensions is provided, the extension array defaults to
* just '.php'.
*
* @param string[] $extensions Array of dot included file extensions to use
* @return ClassLoader Returns self for call chaining
*/
public function setFileExtensions(array $extensions)
{
$this->fileExtensions = $extensions;
$this->finder->setFileExtensions($extensions);
return $this;
}

Expand Down Expand Up @@ -329,95 +330,7 @@ private function load($class)
*/
public function findFile($class)
{
$class = ltrim($class, '\\');

if ($file = $this->searchNamespaces($this->prefixPaths, $class, true)) {
return $file;
}

$class = preg_replace('/_(?=[^\\\\]*$)/', '\\', $class);

if ($file = $this->searchNamespaces($this->basePaths, $class, false)) {
return $file;
} elseif ($this->useIncludePath) {
return $this->searchDirectories(explode(PATH_SEPARATOR, get_include_path()), $class);
}

return false;
}

/**
* Searches for the class file from the namespaces that apply to the class.
* @param array $paths All the namespace specific paths
* @param string $class Canonized full class name
* @param boolean $truncate True to remove the namespace from the path
* @return string|false Path to the class file or false if not found
*/
private function searchNamespaces($paths, $class, $truncate)
{
foreach ($paths as $namespace => $directories) {
if ($fullPath = $this->searchNamespace($class, $namespace, $directories, $truncate)) {
return $fullPath;
}
}

return false;
}

/**
* Searches for the file in the list of directories, if the namespace applies.
* @param string $class Canonized full class name
* @param string $namespace Namespace that applies to the directories
* @param string[] $directories List of directories for the namespace
* @param boolean $truncate True to remove the namespace from the path
* @return string|false Path to the class file or false if not found
*/
private function searchNamespace($class, $namespace, $directories, $truncate)
{
if (strncmp($class, $namespace, strlen($namespace)) !== 0) {
return false;
}

return $this->searchDirectories(
$directories,
$truncate ? substr($class, strlen($namespace)) : $class
);
}

/**
* Searches for the class file in the list of directories.
* @param string[] $directories List of directory paths where to look for the class
* @param string $class Part of the class name that translates to the file name
* @return string|false Path to the class file or false if not found
*/
private function searchDirectories($directories, $class)
{
foreach ($directories as $directory) {
$directory = trim($directory);
$path = preg_replace('/[\\/\\\\]+/', DIRECTORY_SEPARATOR, $directory . '/' . $class);

if ($directory && $fullPath = $this->searchExtensions($path)) {
return $fullPath;
}
}

return false;
}

/**
* Searches for the class file using known file extensions.
* @param string $path Path to the class file without the file extension
* @return string|false Path to the class file or false if not found
*/
private function searchExtensions($path)
{
foreach ($this->fileExtensions as $ext) {
if (file_exists($path . $ext)) {
return $path . $ext;
}
}

return false;
return $this->finder->findFile($class, $this->prefixPaths, $this->basePaths, $this->useIncludePath);
}

/**
Expand Down
141 changes: 141 additions & 0 deletions src/FileFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

namespace Riimu\Kit\ClassLoader;

/**
* Class for finding class files.
* @author Riikka Kalliomäki <riikka.kalliomaki@gmail.com>
* @copyright Copyright (c) 2015, Riikka Kalliomäki
* @license http://opensource.org/licenses/mit-license.php MIT License
*/
class FileFinder
{
/** @var string[] List of file extensions used to find files */
private $fileExtensions;

/**
* Creates a new PathFinder instance.
*/
public function __construct()
{
$this->fileExtensions = ['.php'];
}

/**
* Sets list of dot included file extensions to use for finding files.
*
* If no list of extensions is provided, the extension array defaults to
* just '.php'.
*
* @param string[] $extensions Array of dot included file extensions to use
*/
public function setFileExtensions(array $extensions)
{
$this->fileExtensions = $extensions;
}

/**
* Attempts to find a file for the given class from given paths.
*
* Both lists of paths must be given as arrays with keys indicating the
* namespace. Empty string can be used for the paths that apply to all
* Classes. Each value must be an array of paths.
*
* @param string $class Full name of the class
* @param array $prefixPaths List of paths used for PSR-4 file search
* @param array $basePaths List of paths used for PSR-0 file search
* @param boolean $useIncludePath Whether to use paths in include_path for PSR-0 search or not
* @return string|false Path to the class file or false if not found
*/
public function findFile($class, array $prefixPaths, array $basePaths = [], $useIncludePath = false)
{
if ($file = $this->searchNamespaces($prefixPaths, $class, true)) {
return $file;
}

$class = preg_replace('/_(?=[^\\\\]*$)/', '\\', $class);

if ($file = $this->searchNamespaces($basePaths, $class, false)) {
return $file;
} elseif ($useIncludePath) {
return $this->searchDirectories(explode(PATH_SEPARATOR, get_include_path()), $class);
}

return false;
}

/**
* Searches for the class file from the namespaces that apply to the class.
* @param array $paths All the namespace specific paths
* @param string $class Canonized full class name
* @param boolean $truncate True to remove the namespace from the path
* @return string|false Path to the class file or false if not found
*/
private function searchNamespaces($paths, $class, $truncate)
{
foreach ($paths as $namespace => $directories) {
$canonized = $this->canonizeClass($namespace, $class, $truncate);

if ($canonized && $file = $this->searchDirectories($directories, $canonized)) {
return $file;
}
}

return false;
}

/**
* Matches the class against the namespace and canonizes the name as needed.
* @param string $namespace Namespace to match against
* @param string $class Full name of the class
* @param boolean $truncate Whether to remove the namespace from the class
* @return string|false Canonized class name or false if it does not match the namespace
*/
private function canonizeClass($namespace, $class, $truncate)
{
$class = ltrim($class, '\\');
$namespace = $namespace == '' ? '' : trim($namespace, '\\') . '\\';

if (strncmp($class, $namespace, strlen($namespace)) !== 0) {
return false;
}

return $truncate ? substr($class, strlen($namespace)) : $class;
}

/**
* Searches for the class file in the list of directories.
* @param string[] $directories List of directory paths where to look for the class
* @param string $class Part of the class name that translates to the file name
* @return string|false Path to the class file or false if not found
*/
private function searchDirectories(array $directories, $class)
{
foreach ($directories as $directory) {
$directory = trim($directory);
$path = preg_replace('/[\\/\\\\]+/', DIRECTORY_SEPARATOR, $directory . '/' . $class);

if ($directory && $file = $this->searchExtensions($path)) {
return $file;
}
}

return false;
}

/**
* Searches for the class file using known file extensions.
* @param string $path Path to the class file without the file extension
* @return string|false Path to the class file or false if not found
*/
private function searchExtensions($path)
{
foreach ($this->fileExtensions as $ext) {
if (file_exists($path . $ext)) {
return $path . $ext;
}
}

return false;
}
}
4 changes: 4 additions & 0 deletions tests/tests/ClassLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public function testLoadingViaIncludePath()
$this->assertClassLoads('pathSuccess', $loader, false);

set_include_path(get_include_path() . PATH_SEPARATOR . CLASS_BASE . DIRECTORY_SEPARATOR . 'include_path');

$loader->useIncludePath(false);
$this->assertClassLoads('pathSuccess', $loader, false);
$loader->useIncludePath(true);
$this->assertClassLoads('pathSuccess', $loader, true);

set_include_path($includePath);
Expand Down

0 comments on commit 5ace999

Please sign in to comment.