PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Extension/Finder.php

https://github.com/orchestral/extension
PHP | 286 lines | 158 code | 34 blank | 94 comment | 9 complexity | 3bee384a5ec6b34746572ed1d9226749 MD5 | raw file
  1. <?php namespace Orchestra\Extension;
  2. use RuntimeException;
  3. use Illuminate\Filesystem\Filesystem;
  4. use Illuminate\Support\Arr;
  5. use Illuminate\Support\Collection;
  6. class Finder
  7. {
  8. /**
  9. * Filesystem instance.
  10. *
  11. * @var \Illuminate\Filesystem\Filesystem
  12. */
  13. protected $files;
  14. /**
  15. * Application and base path configuration.
  16. *
  17. * @var array
  18. */
  19. protected $config = array();
  20. /**
  21. * List of paths.
  22. *
  23. * @var array
  24. */
  25. protected $paths = array();
  26. /**
  27. * Default manifest options.
  28. *
  29. * @var array
  30. */
  31. protected $manifestOptions = array(
  32. 'name' => null,
  33. 'description' => null,
  34. 'author' => null,
  35. 'url' => null,
  36. 'version' => '>0',
  37. 'config' => array(),
  38. 'autoload' => array(),
  39. 'provide' => array(),
  40. );
  41. /**
  42. * List of reserved name.
  43. *
  44. * @var array
  45. */
  46. protected $reserved = array(
  47. 'app',
  48. 'orchestra',
  49. 'resources',
  50. 'orchestra/asset',
  51. 'orchestra/auth',
  52. 'orchestra/debug',
  53. 'orchestra/extension',
  54. 'orchestra/facile',
  55. 'orchestra/foundation',
  56. 'orchestra/html',
  57. 'orchestra/memory',
  58. 'orchestra/model',
  59. 'orchestra/optimize',
  60. 'orchestra/platform',
  61. 'orchestra/resources',
  62. 'orchestra/support',
  63. 'orchestra/testbench',
  64. 'orchestra/view',
  65. 'orchestra/widget',
  66. );
  67. /**
  68. * Construct a new finder.
  69. *
  70. * @param \Illuminate\Filesystem\Filesystem $files
  71. * @param array $config
  72. */
  73. public function __construct(Filesystem $files, array $config)
  74. {
  75. $this->files = $files;
  76. $this->config = $config;
  77. $app = rtrim($config['path.app'], '/');
  78. $base = rtrim($config['path.base'], '/');
  79. // In most cases we would only need to concern with the following
  80. // path; application folder, vendor folders and workbench folders.
  81. $this->paths = array(
  82. "{$app}/",
  83. "{$base}/vendor/*/*/",
  84. "{$base}/workbench/*/*/"
  85. );
  86. }
  87. /**
  88. * Add a new path to finder.
  89. *
  90. * @param string $path
  91. * @return Finder
  92. */
  93. public function addPath($path)
  94. {
  95. if (! in_array($path, $this->paths)) {
  96. $this->paths[] = $path;
  97. }
  98. return $this;
  99. }
  100. /**
  101. * Detect available extensions.
  102. *
  103. * @return array
  104. */
  105. public function detect()
  106. {
  107. $extensions = array();
  108. // Loop each path to check if there orchestra.json available within
  109. // the paths. We would only treat packages that include orchestra.json
  110. // as an Orchestra Platform extension.
  111. foreach ($this->paths as $path) {
  112. $manifests = $this->files->glob("{$path}orchestra.json");
  113. // glob() method might return false if there an errors, convert
  114. // the result to an array.
  115. is_array($manifests) || $manifests = array();
  116. foreach ($manifests as $manifest) {
  117. $name = $this->guessExtensionNameFromManifest($manifest, $path);
  118. if (! is_null($name)) {
  119. $extensions[$name] = $this->getManifestContents($manifest);
  120. }
  121. }
  122. }
  123. return new Collection($extensions);
  124. }
  125. /**
  126. * Get manifest contents.
  127. *
  128. * @param string $manifest
  129. * @return array
  130. * @throws ManifestRuntimeException
  131. */
  132. protected function getManifestContents($manifest)
  133. {
  134. $path = $sourcePath = $this->guessExtensionPath($manifest);
  135. $jsonable = json_decode($this->files->get($manifest), true);
  136. // If json_decode fail, due to invalid json format. We going to
  137. // throw an exception so this error can be fixed by the developer
  138. // instead of allowing the application to run with a buggy config.
  139. if (is_null($jsonable)) {
  140. throw new ManifestRuntimeException("Cannot decode file [{$manifest}]");
  141. }
  142. isset($jsonable['path']) && $path = $jsonable['path'];
  143. $paths = array(
  144. 'path' => rtrim($path, '/'),
  145. 'source-path' => rtrim($sourcePath, '/'),
  146. );
  147. // Generate a proper manifest configuration for the extension. This
  148. // would allow other part of the application to use this configuration
  149. // to migrate, load service provider as well as preload some
  150. // configuration.
  151. return array_merge($paths, $this->generateManifestConfig($jsonable));
  152. }
  153. /**
  154. * Generate a proper manifest configuration for the extension. This
  155. * would allow other part of the application to use this configuration
  156. * to migrate, load service provider as well as preload some
  157. * configuration.
  158. *
  159. * @param array $jsonable
  160. * @return array
  161. */
  162. protected function generateManifestConfig(array $jsonable)
  163. {
  164. $manifest = array();
  165. // Assign extension manifest option or provide the default value.
  166. foreach ($this->manifestOptions as $key => $default) {
  167. $manifest["{$key}"] = Arr::get($jsonable, $key, $default);
  168. }
  169. return $manifest;
  170. }
  171. /**
  172. * Guess extension name from manifest.
  173. *
  174. * @param string $manifest
  175. * @param string $path
  176. * @return string
  177. * @throws \RuntimeException
  178. */
  179. public function guessExtensionNameFromManifest($manifest, $path)
  180. {
  181. if (rtrim($this->config['path.app'], '/') === rtrim($path, '/')) {
  182. return 'app';
  183. }
  184. list($vendor, $package) = $namespace = $this->resolveExtensionNamespace($manifest);
  185. if (is_null($vendor) && is_null($package)) {
  186. return null;
  187. }
  188. // Each package should have vendor/package name pattern.
  189. $name = trim(implode('/', $namespace));
  190. if (in_array($name, $this->reserved)) {
  191. throw new RuntimeException("Unable to register reserved name [{$name}] as extension.");
  192. }
  193. return $name;
  194. }
  195. /**
  196. * Guess extension path from manifest file.
  197. *
  198. * @param string $path
  199. * @return string
  200. */
  201. public function guessExtensionPath($path)
  202. {
  203. $path = str_replace('orchestra.json', '', $path);
  204. $app = rtrim($this->config['path.app'], '/');
  205. $base = rtrim($this->config['path.base'], '/');
  206. return str_replace(
  207. array("{$app}/", "{$base}/vendor/", "{$base}/workbench/", "{$base}/"),
  208. array('app::', 'vendor::', 'workbench::', 'base::'),
  209. $path
  210. );
  211. }
  212. /**
  213. * Resolve extension namespace name from manifest.
  214. *
  215. * @param string $manifest
  216. * @return array
  217. */
  218. public function resolveExtensionNamespace($manifest)
  219. {
  220. $vendor = null;
  221. $package = null;
  222. $manifest = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $manifest);
  223. $fragment = explode(DIRECTORY_SEPARATOR, $manifest);
  224. // Remove orchestra.json from fragment as we are only interested with
  225. // the two segment before it.
  226. if (array_pop($fragment) == 'orchestra.json') {
  227. $package = array_pop($fragment);
  228. $vendor = array_pop($fragment);
  229. }
  230. return array($vendor, $package);
  231. }
  232. /**
  233. * Resolve extension path.
  234. *
  235. * @param string $path
  236. * @return string
  237. */
  238. public function resolveExtensionPath($path)
  239. {
  240. $app = rtrim($this->config['path.app'], '/');
  241. $base = rtrim($this->config['path.base'], '/');
  242. return str_replace(
  243. array('app::', 'vendor::', 'workbench::', 'base::'),
  244. array("{$app}/", "{$base}/vendor/", "{$base}/workbench/", "{$base}/"),
  245. $path
  246. );
  247. }
  248. }