Advertisement
Guest User

Untitled

a guest
Jul 19th, 2015
257
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.34 KB | None | 0 0
  1. <?php
  2.  
  3. /*
  4. * This file is part of the puli/repository package.
  5. *
  6. * (c) Bernhard Schussek <bschussek@gmail.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11.  
  12. namespace Puli\Repository;
  13.  
  14. use Countable;
  15. use ArrayIterator;
  16. use Puli\Repository\Api\EditableRepository;
  17. use Puli\Repository\Api\Resource\FilesystemResource;
  18. use Puli\Repository\Api\ResourceCollection;
  19. use Puli\Repository\Resource\Collection\FilesystemResourceCollection;
  20. use Puli\Repository\Api\ResourceNotFoundException;
  21. use Puli\Repository\Api\UnsupportedLanguageException;
  22. use Puli\Repository\Api\UnsupportedResourceException;
  23. use Puli\Repository\Resource\DirectoryResource;
  24. use Puli\Repository\Resource\GenericFilesystemResource;
  25. use Webmozart\Assert\Assert;
  26. use Webmozart\Glob\Glob;
  27. use Webmozart\Glob\Iterator\GlobFilterIterator;
  28. use Webmozart\Glob\Iterator\RegexFilterIterator;
  29. use Webmozart\KeyValueStore\Api\KeyValueStore;
  30. use Webmozart\PathUtil\Path;
  31.  
  32. /**
  33. * An optimized path mapping resource repository.
  34. * When a resource is added, all its children are resolved
  35. * and getting them is much faster.
  36. *
  37. * Resources can be added with the method {@link add()}:
  38. *
  39. * ```php
  40. * use Puli\Repository\OptimizedPathMappingRepository;
  41. *
  42. * $repo = new OptimizedPathMappingRepository();
  43. * $repo->add('/css', new DirectoryResource('/path/to/project/res/css'));
  44. * ```
  45. *
  46. * This repository only supports instances of FilesystemResource.
  47. *
  48. * @since 1.0
  49. * @author Bernhard Schussek <bschussek@gmail.com>
  50. * @author Titouan Galopin <galopintitouan@gmail.com>
  51. */
  52. class OptimizedPathMappingRepository implements EditableRepository
  53. {
  54. /**
  55. * @var KeyValueStore
  56. */
  57. private $store;
  58.  
  59. /**
  60. * Creates a new repository.
  61. *
  62. * @param KeyValueStore $store The store of all the paths.
  63. */
  64. public function __construct(KeyValueStore $store)
  65. {
  66. $this->store = $store;
  67.  
  68. $this->createRoot();
  69. }
  70.  
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function get($path)
  75. {
  76. Assert::stringNotEmpty($path, 'The path must be a non-empty string. Got: %s');
  77. Assert::startsWith($path, '/', 'The path %s is not absolute.');
  78.  
  79. $path = Path::canonicalize($path);
  80.  
  81. if (!$this->store->exists($path)) {
  82. throw ResourceNotFoundException::forPath($path);
  83. }
  84.  
  85. $resource = $this->store->get($path);
  86. $resource->attachTo($this, $path);
  87.  
  88. return $resource;
  89. }
  90.  
  91. /**
  92. * {@inheritdoc}
  93. */
  94. public function find($query, $language = 'glob')
  95. {
  96. if ('glob' !== $language) {
  97. throw UnsupportedLanguageException::forLanguage($language);
  98. }
  99.  
  100. Assert::stringNotEmpty($query, 'The glob must be a non-empty string. Got: %s');
  101. Assert::startsWith($query, '/', 'The glob %s is not absolute.');
  102.  
  103. $query = Path::canonicalize($query);
  104. $resources = new FilesystemResourceCollection();
  105.  
  106. if (Glob::isDynamic($query)) {
  107. $resources = $this->iteratorToCollection($this->getGlobIterator($query));
  108. } elseif ($this->store->exists($query)) {
  109. $resources = new FilesystemResourceCollection(array($this->store->get($query)));
  110. }
  111.  
  112. return $resources;
  113. }
  114.  
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function contains($query, $language = 'glob')
  119. {
  120. if ('glob' !== $language) {
  121. throw UnsupportedLanguageException::forLanguage($language);
  122. }
  123.  
  124. Assert::stringNotEmpty($query, 'The glob must be a non-empty string. Got: %s');
  125. Assert::startsWith($query, '/', 'The glob %s is not absolute.');
  126.  
  127. $query = Path::canonicalize($query);
  128.  
  129. if (Glob::isDynamic($query)) {
  130. $iterator = $this->getGlobIterator($query);
  131. $iterator->rewind();
  132.  
  133. return $iterator->valid();
  134. }
  135.  
  136. return $this->store->exists($query);
  137. }
  138.  
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function add($path, $resource)
  143. {
  144. Assert::stringNotEmpty($path, 'The path must be a non-empty string. Got: %s');
  145. Assert::startsWith($path, '/', 'The path %s is not absolute.');
  146.  
  147. $path = Path::canonicalize($path);
  148.  
  149. if ($resource instanceof ResourceCollection) {
  150. $this->ensureDirectoryExists($path);
  151.  
  152. foreach ($resource as $child) {
  153. $this->addResource($path.'/'.$child->getName(), $child);
  154. }
  155.  
  156. $this->sortStore();
  157.  
  158. return;
  159. }
  160.  
  161. if ($resource instanceof FilesystemResource) {
  162. $this->ensureDirectoryExists(Path::getDirectory($path));
  163. $this->addResource($path, $resource);
  164. $this->sortStore();
  165.  
  166. return;
  167. }
  168.  
  169. throw new UnsupportedResourceException(sprintf(
  170. 'The passed resource must be a FilesystemResource or a ResourceCollection. Got: %s',
  171. is_object($resource) ? get_class($resource) : gettype($resource)
  172. ));
  173. }
  174.  
  175. /**
  176. * {@inheritdoc}
  177. */
  178. public function remove($query, $language = 'glob')
  179. {
  180. $resources = $this->find($query, $language);
  181. $nbOfResources = $this->countStore();
  182.  
  183. // Run the assertion after find(), so that we know that $query is valid
  184. Assert::notEq('', trim($query, '/'), 'The root directory cannot be removed.');
  185.  
  186. foreach ($resources as $resource) {
  187. $this->removeResource($resource);
  188. }
  189.  
  190. return $nbOfResources - $this->countStore();
  191. }
  192.  
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function clear()
  197. {
  198. // Subtract root
  199. $removed = $this->countStore() - 1;
  200.  
  201. $this->store->clear();
  202. $this->createRoot();
  203.  
  204. return $removed;
  205. }
  206.  
  207. /**
  208. * {@inheritdoc}
  209. */
  210. public function listChildren($path)
  211. {
  212. $iterator = $this->getChildIterator($this->get($path));
  213. $children = $this->iteratorToCollection($iterator);
  214.  
  215. return new FilesystemResourceCollection($children);
  216. }
  217.  
  218. /**
  219. * {@inheritdoc}
  220. */
  221. public function hasChildren($path)
  222. {
  223. $iterator = $this->getChildIterator($this->get($path));
  224. $iterator->rewind();
  225.  
  226. return $iterator->valid();
  227. }
  228.  
  229.  
  230. /**
  231. * @param string $path
  232. * @param FilesystemResource $resource
  233. */
  234. private function addResource($path, FilesystemResource $resource)
  235. {
  236. // Don't modify resources attached to other repositories
  237. if ($resource->isAttached()) {
  238. $resource = clone $resource;
  239. }
  240.  
  241. // Read children before attaching the resource to this repository
  242. $children = $resource->listChildren();
  243.  
  244. $resource->attachTo($this, $path);
  245.  
  246. // Add the resource before adding its children, so that the array
  247. // stays sorted
  248. $this->store->set($path, $resource);
  249.  
  250. $basePath = '/' === $path ? $path : $path.'/';
  251.  
  252. foreach ($children as $name => $child) {
  253. $this->addResource($basePath.$name, $child);
  254. }
  255. }
  256.  
  257.  
  258. /**
  259. * @param FilesystemResource $resource
  260. */
  261. private function removeResource(FilesystemResource $resource)
  262. {
  263. $path = $resource->getPath();
  264.  
  265. // Ignore non-existing resources
  266. if (!$this->store->exists($path)) {
  267. return;
  268. }
  269.  
  270. // Recursively register directory contents
  271. foreach ($this->iteratorToCollection($this->getChildIterator($resource)) as $child) {
  272. $this->removeResource($child);
  273. }
  274.  
  275. $this->store->remove($path);
  276.  
  277. // Detach from locator
  278. $resource->detach($this);
  279. }
  280.  
  281. /**
  282. * Returns an iterator for the children of a resource.
  283. *
  284. * @param FilesystemResource $resource The resource.
  285. *
  286. * @return RegexFilterIterator|Resource[] The iterator.
  287. */
  288. private function getChildIterator(FilesystemResource $resource)
  289. {
  290. $staticPrefix = rtrim($resource->getPath(), '/').'/';
  291. $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
  292.  
  293. return new RegexFilterIterator(
  294. $regExp,
  295. $staticPrefix,
  296. new ArrayIterator($this->store->keys())
  297. );
  298. }
  299.  
  300. /**
  301. * Returns an iterator for a glob.
  302. *
  303. * @param string $glob The glob.
  304. *
  305. * @return GlobFilterIterator|Resource[] The iterator.
  306. */
  307. private function getGlobIterator($glob)
  308. {
  309. return new GlobFilterIterator(
  310. $glob,
  311. new ArrayIterator($this->store->keys())
  312. );
  313. }
  314.  
  315. /**
  316. * Recursively creates a directory for a path.
  317. *
  318. * @param string $path A directory path.
  319. * @return DirectoryResource The created resource
  320. */
  321. private function ensureDirectoryExists($path)
  322. {
  323. if (!$this->store->exists($path)) {
  324. // Recursively initialize parent directories
  325. if ($path !== '/') {
  326. $this->ensureDirectoryExists(Path::getDirectory($path));
  327. }
  328.  
  329. $resource = new GenericFilesystemResource($path);
  330. $resource->attachTo($this, $path);
  331.  
  332. $this->store->set($path, $resource);
  333.  
  334. return;
  335. }
  336. }
  337.  
  338. /**
  339. * Transform an iterator of paths into a collection of resources
  340. *
  341. * @param \Iterator $iterator
  342. * @return FilesystemResourceCollection
  343. */
  344. private function iteratorToCollection(\Iterator $iterator)
  345. {
  346. $paths = iterator_to_array($iterator);
  347. $resources = array_values($this->store->getMultiple($paths));
  348.  
  349. return new FilesystemResourceCollection($resources);
  350. }
  351.  
  352. /**
  353. * Count the number of elements in the store
  354. */
  355. private function createRoot()
  356. {
  357. if ($this->store->exists('/')) {
  358. return;
  359. }
  360.  
  361. $root = new GenericFilesystemResource();
  362. $root->attachTo($this, '/');
  363.  
  364. $this->store->set('/', $root);
  365. }
  366.  
  367. /**
  368. * Count the number of elements in the store
  369. */
  370. private function countStore()
  371. {
  372. if ($this->store instanceof Countable) {
  373. return count($this->store);
  374. }
  375.  
  376. return count($this->store->keys());
  377. }
  378.  
  379. /**
  380. * Sort the store by keys
  381. */
  382. private function sortStore()
  383. {
  384. $resources = $this->store->getMultiple($this->store->keys());
  385.  
  386. ksort($resources);
  387.  
  388. $this->store->clear();
  389.  
  390. foreach ($resources as $path => $resource) {
  391. $this->store->set($path, $resource);
  392. }
  393. }
  394. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement