1<?php 2 3declare(strict_types=1); 4 5/** 6 * This file is part of phpDocumentor. 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 * 11 * @link http://phpdoc.org 12 */ 13 14namespace phpDocumentor\Reflection\DocBlock; 15 16use phpDocumentor\Reflection\DocBlock\Tags\Example; 17use function array_slice; 18use function file; 19use function getcwd; 20use function implode; 21use function is_readable; 22use function rtrim; 23use function sprintf; 24use function trim; 25use const DIRECTORY_SEPARATOR; 26 27/** 28 * Class used to find an example file's location based on a given ExampleDescriptor. 29 */ 30class ExampleFinder 31{ 32 /** @var string */ 33 private $sourceDirectory = ''; 34 35 /** @var string[] */ 36 private $exampleDirectories = []; 37 38 /** 39 * Attempts to find the example contents for the given descriptor. 40 */ 41 public function find(Example $example) : string 42 { 43 $filename = $example->getFilePath(); 44 45 $file = $this->getExampleFileContents($filename); 46 if (!$file) { 47 return sprintf('** File not found : %s **', $filename); 48 } 49 50 return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); 51 } 52 53 /** 54 * Registers the project's root directory where an 'examples' folder can be expected. 55 */ 56 public function setSourceDirectory(string $directory = '') : void 57 { 58 $this->sourceDirectory = $directory; 59 } 60 61 /** 62 * Returns the project's root directory where an 'examples' folder can be expected. 63 */ 64 public function getSourceDirectory() : string 65 { 66 return $this->sourceDirectory; 67 } 68 69 /** 70 * Registers a series of directories that may contain examples. 71 * 72 * @param string[] $directories 73 */ 74 public function setExampleDirectories(array $directories) : void 75 { 76 $this->exampleDirectories = $directories; 77 } 78 79 /** 80 * Returns a series of directories that may contain examples. 81 * 82 * @return string[] 83 */ 84 public function getExampleDirectories() : array 85 { 86 return $this->exampleDirectories; 87 } 88 89 /** 90 * Attempts to find the requested example file and returns its contents or null if no file was found. 91 * 92 * This method will try several methods in search of the given example file, the first one it encounters is 93 * returned: 94 * 95 * 1. Iterates through all examples folders for the given filename 96 * 2. Checks the source folder for the given filename 97 * 3. Checks the 'examples' folder in the current working directory for examples 98 * 4. Checks the path relative to the current working directory for the given filename 99 * 100 * @return string[] all lines of the example file 101 */ 102 private function getExampleFileContents(string $filename) : ?array 103 { 104 $normalizedPath = null; 105 106 foreach ($this->exampleDirectories as $directory) { 107 $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); 108 if (is_readable($exampleFileFromConfig)) { 109 $normalizedPath = $exampleFileFromConfig; 110 break; 111 } 112 } 113 114 if (!$normalizedPath) { 115 if (is_readable($this->getExamplePathFromSource($filename))) { 116 $normalizedPath = $this->getExamplePathFromSource($filename); 117 } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { 118 $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); 119 } elseif (is_readable($filename)) { 120 $normalizedPath = $filename; 121 } 122 } 123 124 $lines = $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : false; 125 126 return $lines !== false ? $lines : null; 127 } 128 129 /** 130 * Get example filepath based on the example directory inside your project. 131 */ 132 private function getExamplePathFromExampleDirectory(string $file) : string 133 { 134 return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; 135 } 136 137 /** 138 * Returns a path to the example file in the given directory.. 139 */ 140 private function constructExamplePath(string $directory, string $file) : string 141 { 142 return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; 143 } 144 145 /** 146 * Get example filepath based on sourcecode. 147 */ 148 private function getExamplePathFromSource(string $file) : string 149 { 150 return sprintf( 151 '%s%s%s', 152 trim($this->getSourceDirectory(), '\\/'), 153 DIRECTORY_SEPARATOR, 154 trim($file, '"') 155 ); 156 } 157} 158