GearmanCacheWrapper.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. /**
  3. * Gearman Bundle for Symfony2
  4. *
  5. * @author Marc Morera <yuhu@mmoreram.com>
  6. * @since 2013
  7. */
  8. namespace Mmoreram\GearmanBundle\Service;
  9. use Doctrine\Common\Annotations\Reader;
  10. use Doctrine\Common\Annotations\AnnotationRegistry;
  11. use Symfony\Component\HttpKernel\Kernel;
  12. use Doctrine\Common\Cache\Cache;
  13. use Symfony\Component\Finder\Finder;
  14. use Doctrine\Common\Annotations\SimpleAnnotationReader;
  15. use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
  16. use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
  17. use Mmoreram\GearmanBundle\Module\WorkerCollection;
  18. use Mmoreram\GearmanBundle\Module\WorkerClass as Worker;
  19. use Mmoreram\GearmanBundle\Driver\Gearman\Work as WorkAnnotation;
  20. use ReflectionClass;
  21. /**
  22. * Gearman cache loader class
  23. *
  24. * @author Marc Morera <yuhu@mmoreram.com>
  25. */
  26. class GearmanCacheWrapper implements CacheClearerInterface, CacheWarmerInterface
  27. {
  28. /**
  29. * @var Array
  30. *
  31. * Bundles loaded by kernel
  32. */
  33. private $kernelBundles;
  34. /**
  35. * @var Kernel
  36. *
  37. * Kernel object
  38. */
  39. private $kernel;
  40. /**
  41. * @var Array
  42. *
  43. * Bundles available to perform search
  44. */
  45. private $bundles;
  46. /**
  47. * @var Array
  48. *
  49. * paths to search on
  50. */
  51. private $paths = array();
  52. /**
  53. * @var Array
  54. *
  55. * paths to ignore
  56. */
  57. private $excludedPaths = array();
  58. /**
  59. * @var GearmanCache
  60. *
  61. * Gearman Cache
  62. */
  63. private $cache;
  64. /**
  65. * @var string
  66. *
  67. * Gearman cache id
  68. */
  69. private $cacheId;
  70. /**
  71. * @var array
  72. *
  73. * WorkerCollection with all workers and jobs available
  74. */
  75. private $workerCollection;
  76. /**
  77. * @var array
  78. *
  79. * Collection of servers to connect
  80. */
  81. private $servers;
  82. /**
  83. * @var array
  84. *
  85. * Default settings defined by user in config.yml
  86. */
  87. private $defaultSettings;
  88. /**
  89. * Return workerCollection
  90. *
  91. * @return array all available workers
  92. */
  93. public function getWorkers()
  94. {
  95. return $this->workerCollection;
  96. }
  97. /**
  98. * Construct method
  99. *
  100. * @param Kernel $kernel Kernel instance
  101. * @param Cache $cache Cache
  102. * @param string $cacheId Cache id where to save parsing data
  103. * @param array $bundles Bundle array where to parse workers, defined on condiguration
  104. * @param array $servers Server list defined on configuration
  105. * @param array $defaultSettings Default settings defined on configuration
  106. */
  107. public function __construct(Kernel $kernel, Cache $cache, $cacheId, array $bundles, array $servers, array $defaultSettings)
  108. {
  109. $this->kernelBundles = $kernel->getBundles();
  110. $this->kernel = $kernel;
  111. $this->bundles = $bundles;
  112. $this->cache = $cache;
  113. $this->cacheId = $cacheId;
  114. $this->servers = $servers;
  115. $this->defaultSettings = $defaultSettings;
  116. }
  117. /**
  118. * loads Gearman cache, only if is not loaded yet
  119. *
  120. * @return GearmanCacheLoader self Object
  121. */
  122. public function load()
  123. {
  124. if ($this->cache->contains($this->cacheId)) {
  125. $this->workerCollection = $this->cache->fetch($this->cacheId);
  126. } else {
  127. $this->workerCollection = $this->parseNamespaceMap()->toArray();
  128. $this->cache->save($this->cacheId, $this->workerCollection);
  129. }
  130. return $this;
  131. }
  132. /**
  133. * flush all cache
  134. *
  135. * @return GearmanCacheLoader self Object
  136. */
  137. public function flush()
  138. {
  139. $this->cache->delete($this->cacheId);
  140. return $this;
  141. }
  142. /**
  143. * Return Gearman bundle settings, previously loaded by method load()
  144. *
  145. * If settings are not loaded, a SettingsNotLoadedException Exception is thrown
  146. */
  147. public function loadNamespaceMap()
  148. {
  149. /**
  150. * Iteratinc all bundle settings
  151. */
  152. foreach ($this->bundles as $bundleSettings) {
  153. if (!$bundleSettings['active']) {
  154. break;
  155. }
  156. $bundleNamespace = $bundleSettings['name'];
  157. $bundlePath = $this->kernelBundles[$bundleNamespace]->getPath();
  158. if (!empty($bundleSettings['include'])) {
  159. foreach ($bundleSettings['include'] as $include) {
  160. $this->paths[] = rtrim(rtrim($bundlePath, '/') . '/' . $include, '/') . '/';
  161. }
  162. } else {
  163. /**
  164. * If no include is set, include all namespace
  165. */
  166. $this->paths[] = rtrim($bundlePath, '/') . '/';
  167. }
  168. foreach ($bundleSettings['ignore'] as $ignore) {
  169. $this->excludedPaths[] = trim($ignore, '/');
  170. }
  171. }
  172. }
  173. /**
  174. * Perform a parsing inside all namespace map
  175. *
  176. * @return WorkerCollection collection of all info
  177. */
  178. private function parseNamespaceMap()
  179. {
  180. AnnotationRegistry::registerFile($this->kernel->locateResource("@GearmanBundle/Driver/Gearman/Work.php"));
  181. AnnotationRegistry::registerFile($this->kernel->locateResource("@GearmanBundle/Driver/Gearman/Job.php"));
  182. $reader = new SimpleAnnotationReader();
  183. $reader->addNamespace('Mmoreram\GearmanBundle\Driver');
  184. $workerCollection = new WorkerCollection;
  185. if (!empty($this->paths)) {
  186. $finder = new Finder();
  187. $finder
  188. ->files()
  189. ->followLinks()
  190. ->exclude($this->excludedPaths)
  191. ->in($this->paths)
  192. ->name('*.php');
  193. $workerCollection = $this->parseFiles($finder, $reader);
  194. }
  195. return $workerCollection;
  196. }
  197. /**
  198. * Load all workers with their jobs
  199. *
  200. * @param Finder $finder Finder
  201. * @param Reader $reader Reader
  202. *
  203. * @return WorkerCollection collection of all info
  204. */
  205. private function parseFiles(Finder $finder, Reader $reader)
  206. {
  207. $workerCollection = new WorkerCollection;
  208. /**
  209. * Every file found is parsed
  210. */
  211. foreach ($finder as $file) {
  212. /**
  213. * File is accepted to be parsed
  214. */
  215. $classNamespace = $this->getFileClassNamespace($file->getRealpath());
  216. $reflClass = new ReflectionClass($classNamespace);
  217. $classAnnotations = $reader->getClassAnnotations($reflClass);
  218. /**
  219. * Every annotation found is parsed
  220. */
  221. foreach ($classAnnotations as $annot) {
  222. /**
  223. * Annotation is only laoded if is typeof WorkAnnotation
  224. */
  225. if ($annot instanceof WorkAnnotation) {
  226. /**
  227. * Creates new Worker element with all its Job data
  228. */
  229. $worker = new Worker($annot, $reflClass, $reader, $this->servers, $this->defaultSettings);
  230. $workerCollection->add($worker);
  231. }
  232. }
  233. }
  234. return $workerCollection;
  235. }
  236. /**
  237. * Cache clear implementation
  238. *
  239. * @param string $cacheDir The cache directory
  240. */
  241. public function clear($cacheDir)
  242. {
  243. $this->flush();
  244. }
  245. /**
  246. * Warms up the cache.
  247. *
  248. * @param string $cacheDir The cache directory
  249. */
  250. public function warmUp($cacheDir)
  251. {
  252. $this->load();
  253. }
  254. /**
  255. * Checks whether this warmer is optional or not.
  256. *
  257. * Optional warmers can be ignored on certain conditions.
  258. *
  259. * A warmer should return true if the cache can be
  260. * generated incrementally and on-demand.
  261. *
  262. * As GearmanBundle loads cache incrementaly so is optional
  263. *
  264. * @return Boolean true if the warmer is optional, false otherwise
  265. */
  266. public function isOptional()
  267. {
  268. return true;
  269. }
  270. /**
  271. * Returns the full class name for the first class in the file.
  272. *
  273. * @param string $file A PHP file path
  274. *
  275. * @return string|false Full class name if found, false otherwise
  276. */
  277. protected function getFileClassNamespace($file)
  278. {
  279. $class = false;
  280. $namespace = false;
  281. $tokens = token_get_all(file_get_contents($file));
  282. for ($i = 0, $count = count($tokens); $i < $count; $i++) {
  283. $token = $tokens[$i];
  284. if (!is_array($token)) {
  285. continue;
  286. }
  287. if (true === $class && T_STRING === $token[0]) {
  288. return $namespace.'\\'.$token[1];
  289. }
  290. if (true === $namespace && T_STRING === $token[0]) {
  291. $namespace = '';
  292. do {
  293. $namespace .= $token[1];
  294. $token = $tokens[++$i];
  295. } while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING)));
  296. }
  297. if (T_CLASS === $token[0]) {
  298. $class = true;
  299. }
  300. if (T_NAMESPACE === $token[0]) {
  301. $namespace = true;
  302. }
  303. }
  304. return false;
  305. }
  306. }