Factory.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * Factory that creates cache frontend instances based on options
  8. */
  9. namespace Magento\Framework\App\Cache\Frontend;
  10. use Magento\Framework\App\Filesystem\DirectoryList;
  11. use Magento\Framework\Filesystem;
  12. /**
  13. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  14. */
  15. class Factory
  16. {
  17. /**
  18. * Default cache entry lifetime
  19. */
  20. const DEFAULT_LIFETIME = 7200;
  21. /**
  22. * Caching params, that applied for all cache frontends regardless of type
  23. */
  24. const PARAM_CACHE_FORCED_OPTIONS = 'cache_options';
  25. /**
  26. * @var \Magento\Framework\ObjectManagerInterface
  27. */
  28. private $_objectManager;
  29. /**
  30. * @var Filesystem
  31. */
  32. private $_filesystem;
  33. /**
  34. * Cache options to be enforced for all instances being created
  35. *
  36. * @var array
  37. */
  38. private $_enforcedOptions = [];
  39. /**
  40. * Configuration of decorators that are to be applied to every cache frontend being instantiated, format:
  41. * array(
  42. * array('class' => '<decorator_class>', 'arguments' => array()),
  43. * ...
  44. * )
  45. *
  46. * @var array
  47. */
  48. private $_decorators = [];
  49. /**
  50. * Default cache backend type
  51. *
  52. * @var string
  53. */
  54. protected $_defaultBackend = 'Cm_Cache_Backend_File';
  55. /**
  56. * Options for default backend
  57. *
  58. * @var array
  59. */
  60. protected $_backendOptions = [
  61. 'hashed_directory_level' => 1,
  62. 'file_name_prefix' => 'mage',
  63. ];
  64. /**
  65. * Resource
  66. *
  67. * @var \Magento\Framework\App\ResourceConnection
  68. */
  69. protected $_resource;
  70. /**
  71. * @param \Magento\Framework\ObjectManagerInterface $objectManager
  72. * @param Filesystem $filesystem
  73. * @param \Magento\Framework\App\ResourceConnection $resource
  74. * @param array $enforcedOptions
  75. * @param array $decorators
  76. */
  77. public function __construct(
  78. \Magento\Framework\ObjectManagerInterface $objectManager,
  79. Filesystem $filesystem,
  80. \Magento\Framework\App\ResourceConnection $resource,
  81. array $enforcedOptions = [],
  82. array $decorators = []
  83. ) {
  84. $this->_objectManager = $objectManager;
  85. $this->_filesystem = $filesystem;
  86. $this->_resource = $resource;
  87. $this->_enforcedOptions = $enforcedOptions;
  88. $this->_decorators = $decorators;
  89. }
  90. /**
  91. * Return newly created cache frontend instance
  92. *
  93. * @param array $options
  94. * @return \Magento\Framework\Cache\FrontendInterface
  95. */
  96. public function create(array $options)
  97. {
  98. $options = $this->_getExpandedOptions($options);
  99. foreach (['backend_options', 'slow_backend_options'] as $section) {
  100. if (!empty($options[$section]['cache_dir'])) {
  101. $directory = $this->_filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
  102. $directory->create($options[$section]['cache_dir']);
  103. $options[$section]['cache_dir'] = $directory->getAbsolutePath($options[$section]['cache_dir']);
  104. }
  105. }
  106. $idPrefix = isset($options['id_prefix']) ? $options['id_prefix'] : '';
  107. if (!$idPrefix && isset($options['prefix'])) {
  108. $idPrefix = $options['prefix'];
  109. }
  110. if (empty($idPrefix)) {
  111. $configDirPath = $this->_filesystem->getDirectoryRead(DirectoryList::CONFIG)->getAbsolutePath();
  112. $idPrefix =
  113. substr(md5($configDirPath), 0, 3) . '_';
  114. }
  115. $options['frontend_options']['cache_id_prefix'] = $idPrefix;
  116. $backend = $this->_getBackendOptions($options);
  117. $frontend = $this->_getFrontendOptions($options);
  118. // Start profiling
  119. $profilerTags = [
  120. 'group' => 'cache',
  121. 'operation' => 'cache:create',
  122. 'frontend_type' => $frontend['type'],
  123. 'backend_type' => $backend['type'],
  124. ];
  125. \Magento\Framework\Profiler::start('cache_frontend_create', $profilerTags);
  126. /** @var $result \Magento\Framework\Cache\Frontend\Adapter\Zend */
  127. $result = $this->_objectManager->create(
  128. \Magento\Framework\Cache\Frontend\Adapter\Zend::class,
  129. [
  130. 'frontendFactory' => function () use ($frontend, $backend) {
  131. return \Zend_Cache::factory(
  132. $frontend['type'],
  133. $backend['type'],
  134. $frontend,
  135. $backend['options'],
  136. true,
  137. true,
  138. true
  139. );
  140. }
  141. ]
  142. );
  143. $result = $this->_applyDecorators($result);
  144. // stop profiling
  145. \Magento\Framework\Profiler::stop('cache_frontend_create');
  146. return $result;
  147. }
  148. /**
  149. * Return options expanded with enforced values
  150. *
  151. * @param array $options
  152. * @return array
  153. */
  154. private function _getExpandedOptions(array $options)
  155. {
  156. return array_replace_recursive($options, $this->_enforcedOptions);
  157. }
  158. /**
  159. * Apply decorators to a cache frontend instance and return the topmost one
  160. *
  161. * @param \Magento\Framework\Cache\FrontendInterface $frontend
  162. * @return \Magento\Framework\Cache\FrontendInterface
  163. * @throws \LogicException
  164. * @throws \UnexpectedValueException
  165. */
  166. private function _applyDecorators(\Magento\Framework\Cache\FrontendInterface $frontend)
  167. {
  168. foreach ($this->_decorators as $decoratorConfig) {
  169. if (!isset($decoratorConfig['class'])) {
  170. throw new \LogicException('Class has to be specified for a cache frontend decorator.');
  171. }
  172. $decoratorClass = $decoratorConfig['class'];
  173. $decoratorParams = isset($decoratorConfig['parameters']) ? $decoratorConfig['parameters'] : [];
  174. $decoratorParams['frontend'] = $frontend;
  175. // conventionally, 'frontend' argument is a decoration subject
  176. $frontend = $this->_objectManager->create($decoratorClass, $decoratorParams);
  177. if (!$frontend instanceof \Magento\Framework\Cache\FrontendInterface) {
  178. throw new \UnexpectedValueException('Decorator has to implement the cache frontend interface.');
  179. }
  180. }
  181. return $frontend;
  182. }
  183. /**
  184. * Get cache backend options. Result array contain backend type ('type' key) and backend options ('options')
  185. *
  186. * @param array $cacheOptions
  187. * @return array
  188. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  189. * @SuppressWarnings(PHPMD.NPathComplexity)
  190. */
  191. protected function _getBackendOptions(array $cacheOptions)
  192. {
  193. $enableTwoLevels = false;
  194. $type = isset($cacheOptions['backend']) ? $cacheOptions['backend'] : $this->_defaultBackend;
  195. if (isset($cacheOptions['backend_options']) && is_array($cacheOptions['backend_options'])) {
  196. $options = $cacheOptions['backend_options'];
  197. } else {
  198. $options = [];
  199. }
  200. $backendType = false;
  201. switch (strtolower($type)) {
  202. case 'sqlite':
  203. if (extension_loaded('sqlite') && isset($options['cache_db_complete_path'])) {
  204. $backendType = 'Sqlite';
  205. }
  206. break;
  207. case 'memcached':
  208. if (extension_loaded('memcached')) {
  209. if (isset($cacheOptions['memcached'])) {
  210. $options = $cacheOptions['memcached'];
  211. }
  212. $enableTwoLevels = true;
  213. $backendType = 'Libmemcached';
  214. } elseif (extension_loaded('memcache')) {
  215. if (isset($cacheOptions['memcached'])) {
  216. $options = $cacheOptions['memcached'];
  217. }
  218. $enableTwoLevels = true;
  219. $backendType = 'Memcached';
  220. }
  221. break;
  222. case 'apc':
  223. if (extension_loaded('apc') && ini_get('apc.enabled')) {
  224. $enableTwoLevels = true;
  225. $backendType = 'Apc';
  226. }
  227. break;
  228. case 'xcache':
  229. if (extension_loaded('xcache')) {
  230. $enableTwoLevels = true;
  231. $backendType = 'Xcache';
  232. }
  233. break;
  234. case 'eaccelerator':
  235. case 'varien_cache_backend_eaccelerator':
  236. if (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) {
  237. $enableTwoLevels = true;
  238. $backendType = \Magento\Framework\Cache\Backend\Eaccelerator::class;
  239. }
  240. break;
  241. case 'database':
  242. $backendType = \Magento\Framework\Cache\Backend\Database::class;
  243. $options = $this->_getDbAdapterOptions();
  244. break;
  245. case 'remote_synchronized_cache':
  246. $backendType = \Magento\Framework\Cache\Backend\RemoteSynchronizedCache::class;
  247. $options['remote_backend'] = \Magento\Framework\Cache\Backend\Database::class;
  248. $options['remote_backend_options'] = $this->_getDbAdapterOptions();
  249. $options['local_backend'] = \Cm_Cache_Backend_File::class;
  250. $cacheDir = $this->_filesystem->getDirectoryWrite(DirectoryList::CACHE);
  251. $options['local_backend_options']['cache_dir'] = $cacheDir->getAbsolutePath();
  252. $cacheDir->create();
  253. break;
  254. default:
  255. if ($type != $this->_defaultBackend) {
  256. try {
  257. if (class_exists($type, true)) {
  258. $implements = class_implements($type, true);
  259. if (in_array('Zend_Cache_Backend_Interface', $implements)) {
  260. $backendType = $type;
  261. }
  262. }
  263. } catch (\Exception $e) {
  264. }
  265. }
  266. }
  267. if (!$backendType) {
  268. $backendType = $this->_defaultBackend;
  269. $cacheDir = $this->_filesystem->getDirectoryWrite(DirectoryList::CACHE);
  270. $this->_backendOptions['cache_dir'] = $cacheDir->getAbsolutePath();
  271. $cacheDir->create();
  272. }
  273. foreach ($this->_backendOptions as $option => $value) {
  274. if (!array_key_exists($option, $options)) {
  275. $options[$option] = $value;
  276. }
  277. }
  278. $backendOptions = ['type' => $backendType, 'options' => $options];
  279. if ($enableTwoLevels) {
  280. $backendOptions = $this->_getTwoLevelsBackendOptions($backendOptions, $cacheOptions);
  281. }
  282. return $backendOptions;
  283. }
  284. /**
  285. * Get options for database backend type
  286. *
  287. * @return array
  288. */
  289. protected function _getDbAdapterOptions()
  290. {
  291. $options['adapter_callback'] = function () {
  292. return $this->_resource->getConnection();
  293. };
  294. $options['data_table_callback'] = function () {
  295. return $this->_resource->getTableName('cache');
  296. };
  297. $options['tags_table_callback'] = function () {
  298. return $this->_resource->getTableName('cache_tag');
  299. };
  300. return $options;
  301. }
  302. /**
  303. * Initialize two levels backend model options
  304. *
  305. * @param array $fastOptions fast level backend type and options
  306. * @param array $cacheOptions all cache options
  307. * @return array
  308. */
  309. protected function _getTwoLevelsBackendOptions($fastOptions, $cacheOptions)
  310. {
  311. $options = [];
  312. $options['fast_backend'] = $fastOptions['type'];
  313. $options['fast_backend_options'] = $fastOptions['options'];
  314. $options['fast_backend_custom_naming'] = true;
  315. $options['fast_backend_autoload'] = true;
  316. $options['slow_backend_custom_naming'] = true;
  317. $options['slow_backend_autoload'] = true;
  318. if (isset($cacheOptions['auto_refresh_fast_cache'])) {
  319. $options['auto_refresh_fast_cache'] = (bool)$cacheOptions['auto_refresh_fast_cache'];
  320. } else {
  321. $options['auto_refresh_fast_cache'] = false;
  322. }
  323. if (isset($cacheOptions['slow_backend'])) {
  324. $options['slow_backend'] = $cacheOptions['slow_backend'];
  325. } else {
  326. $options['slow_backend'] = $this->_defaultBackend;
  327. }
  328. if (isset($cacheOptions['slow_backend_options'])) {
  329. $options['slow_backend_options'] = $cacheOptions['slow_backend_options'];
  330. } else {
  331. $options['slow_backend_options'] = $this->_backendOptions;
  332. }
  333. if ($options['slow_backend'] == 'database') {
  334. $options['slow_backend'] = \Magento\Framework\Cache\Backend\Database::class;
  335. $options['slow_backend_options'] = $this->_getDbAdapterOptions();
  336. if (isset($cacheOptions['slow_backend_store_data'])) {
  337. $options['slow_backend_options']['store_data'] = (bool)$cacheOptions['slow_backend_store_data'];
  338. } else {
  339. $options['slow_backend_options']['store_data'] = false;
  340. }
  341. }
  342. $backend = ['type' => 'TwoLevels', 'options' => $options];
  343. return $backend;
  344. }
  345. /**
  346. * Get options of cache frontend (options of \Zend_Cache_Core)
  347. *
  348. * @param array $cacheOptions
  349. * @return array
  350. * @SuppressWarnings(PHPMD.NPathComplexity)
  351. */
  352. protected function _getFrontendOptions(array $cacheOptions)
  353. {
  354. $options = isset($cacheOptions['frontend_options']) ? $cacheOptions['frontend_options'] : [];
  355. if (!array_key_exists('caching', $options)) {
  356. $options['caching'] = true;
  357. }
  358. if (!array_key_exists('lifetime', $options)) {
  359. $options['lifetime'] = isset(
  360. $cacheOptions['lifetime']
  361. ) ? $cacheOptions['lifetime'] : self::DEFAULT_LIFETIME;
  362. }
  363. if (!array_key_exists('automatic_cleaning_factor', $options)) {
  364. $options['automatic_cleaning_factor'] = 0;
  365. }
  366. $options['type'] =
  367. isset($cacheOptions['frontend']) ? $cacheOptions['frontend'] : \Magento\Framework\Cache\Core::class;
  368. return $options;
  369. }
  370. }