Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/updates/update_m1740162029/main/lib/data/
Upload File :
Current File : /var/www/homesaver/www/bitrix/updates/update_m1740162029/main/lib/data/cacheengine.php

<?php
namespace Bitrix\Main\Data;

use Bitrix\Main\Config;
use Bitrix\Main\Application;

abstract class CacheEngine implements CacheEngineInterface, CacheEngineStatInterface, LocalStorage\Storage\CacheEngineInterface
{
	const BX_BASE_LIST = '|bx_base_list|';
	const BX_DIR_LIST = '|bx_dir_list|';

	protected static \Redis|\Memcache|null|\Memcached $engine = null;
	protected static array $locks = [];
	protected static bool $isConnected = false;
	protected static array $baseDirVersion = [];
	protected string $sid = 'BX';
	protected bool $useLock = false;
	protected int $ttlMultiplier = 2;
	protected int $ttlOld = 60;
	protected bool $old = false;
	protected bool $fullClean = false;

	/** Cache stat */
	private int $written = 0;
	private int $read = 0;
	private string $path = '';

	abstract public function getConnectionName(): string;
	abstract public static function getConnectionClass();

	abstract public function set($key, $ttl, $value);
	abstract public function get($key);
	abstract public function del($key);

	abstract public function setNotExists($key, $ttl , $value);
	abstract public function checkInSet($key, $value): bool;
	abstract public function addToSet($key, $value);
	abstract public function getSet($key): array;
	abstract public function delFromSet($key, $member);

	abstract public function deleteBySet($key, $prefix = '');

	/**
	 * CacheEngine constructor.
	 * @param array $options Cache options.
	 */
	public function __construct(array $options = [])
	{
		$hash = sha1(serialize($options));

		static $config = [];
		if (self::$engine == null || empty($config[$hash]))
		{
				$config[$hash] = $this->configure($options);
		}

		$this->connect($config[$hash]);
	}

	protected function connect($config)
	{
		$connectionPool = Application::getInstance()->getConnectionPool();
		$connectionPool->setConnectionParameters($this->getConnectionName(), $config);

		/** @var RedisConnection|MemcacheConnection|MemcachedConnection $engineConnection */
		$engineConnection = $connectionPool->getConnection($this->getConnectionName());
		self::$engine = $engineConnection->getResource();
		self::$isConnected = $engineConnection->isConnected();
	}

	protected function configure($options = []): array
	{
		$config = [];
		$cacheConfig = Config\Configuration::getValue('cache');

		if (!$cacheConfig || !is_array($cacheConfig))
		{
			return $config;
		}

		if (isset($options['type']))
		{
			$type = $options['type'];
		}
		else
		{
			if (is_array($cacheConfig['type']) && is_set($cacheConfig['type']['extension']))
			{
				$type = $cacheConfig['type']['extension'];
			}
			else
			{
				$type = $cacheConfig['type'];
			}
		}

		$config['type'] = $type;
		$config['className'] = static::getConnectionClass();

		if (!isset($config['servers']) || !is_array($config['servers']))
		{
			$config['servers'] = [];
		}

		if (isset($cacheConfig[$type]) && is_array($cacheConfig[$type]) && !empty($cacheConfig[$type]['host']))
		{
			$config['servers'][] = [
				'host' => $cacheConfig[$type]['host'],
				'port' => (int) ($cacheConfig[$type]['port'] ?? 0)
			];
		}

		// Settings from .settings.php
		if (isset($cacheConfig['servers']) && is_array($cacheConfig['servers']))
		{
			$config['servers'] = array_merge($config['servers'], $cacheConfig['servers']);
		}

		// Setting from cluster config
		if (isset($options['servers']) && is_array($options['servers']))
		{
			$config['servers'] = array_merge($config['servers'], $options['servers']);
		}

		if (isset($options['actual_data']))
		{
			$cacheConfig['actual_data'] = $options['actual_data'];
		}

		if (isset($cacheConfig['use_lock']))
		{
			$this->useLock = (bool) $cacheConfig['use_lock'];
		}

		if (isset($cacheConfig['sid']) && ($cacheConfig['sid'] != ''))
		{
			$this->sid = $cacheConfig['sid'];
		}

		// Only redis
		if (isset($cacheConfig['serializer']))
		{
			$config['serializer'] = (int) $cacheConfig['serializer'];
		}

		if (isset($cacheConfig['connectionTimeout']))
		{
			$config['connectionTimeout'] = (int) $cacheConfig['connectionTimeout'];
		}

		$config['persistent'] = true;
		if (isset($cacheConfig['persistent']) && $cacheConfig['persistent'] == 0)
		{
			$config['persistent'] = false;
		}

		if (isset($cacheConfig['actual_data']) && !$this->useLock)
		{
			$this->useLock = !$cacheConfig['actual_data'];
		}

		if (!$this->useLock)
		{
			$this->ttlMultiplier = 1;
		}

		if (isset($cacheConfig['ttl_multiplier']) && $this->useLock)
		{
			$this->ttlMultiplier = (int) $cacheConfig['ttl_multiplier'];
			if ($this->ttlMultiplier < 1)
			{
				$this->ttlMultiplier = 1;
			}
		}

		if (isset($cacheConfig['full_clean']))
		{
			$this->fullClean = (bool) $cacheConfig['full_clean'];
		}

		if (isset($cacheConfig['ttlOld']) && (int) $cacheConfig['ttlOld'] > 0)
		{
			$this->ttlOld = (int) $cacheConfig['ttlOld'];
		}

		$this->sid .= '|v1|';

		return $config;
	}

	/**
	 * Returns number of bytes read from cache.
	 * @return integer
	 */
	public function getReadBytes()
	{
		return $this->read;
	}

	/**
	 * Returns number of bytes written to cache.
	 * @return integer
	 */
	public function getWrittenBytes()
	{
		return $this->written;
	}

	/**
	 * Returns physical file path after read or write operation.
	 * @return string
	 */
	public function getCachePath()
	{
		return $this->path;
	}

	/**
	 * Tries to put non-blocking exclusive lock on the cache entry.
	 * Returns true on success.
	 *
	 * @param string $key Calculated cache key.
	 * @param integer $ttl Expiration period in seconds.
	 *
	 * @return boolean
	 */
	protected function lock(string $key = '', int $ttl = 0): bool
	{
		if ($key == '')
		{
			return false;
		}

		$key .= '~';
		if (isset(self::$locks[$key]))
		{
			return true;
		}
		else
		{
			if ($this->setNotExists($key, $ttl, $this->ttlOld))
			{
				self::$locks[$key] = true;
				return true;
			}
		}
		return false;
	}

	/**
	 * Releases the lock obtained by lock method.
	 * @param string $key Calculated cache key.
	 * @return void
	 */
	protected function unlock(string $key = ''): void
	{
		if ($key != '')
		{
			$key .= '~';
			$this->del($key);
			unset(self::$locks[$key]);
		}
	}

	/**
	 * Closes opened connection.
	 * @return void
	 */
	function close(): void
	{
		if (self::$engine != null)
		{
			self::$engine->close();
			self::$engine = null;
		}
	}

	/**
	 * Returns true if cache can be read or written.
	 * @return bool
	 */
	public function isAvailable()
	{
		return self::$isConnected;
	}

	/**
	 * Returns true if cache has been expired.
	 * Stub function always returns true.
	 * @param string $path Absolute physical path.
	 * @return boolean
	 */
	public function isCacheExpired($path)
	{
		return false;
	}

	protected function getPartition($key): string
	{
		return '|' . substr(sha1($key), 0, 4) . '|';
	}

	/**
	 * Return InitDirVersion
	 *
	 * @param bool|string $baseDir Base cache directory (usually /bitrix/cache).
	 * @param bool|string $initDir Directory within base.
	 * @return string
	 */
	protected function getInitDirVersion($baseDir, $initDir = false): string
	{
		return sha1($baseDir . '|' . $initDir);
	}

	/**
	 * Return BaseDirVersion
	 * @param bool|string $baseDir Base cache directory (usually /bitrix/cache).
	 *
	 * @return string
	 */
	protected function getBaseDirVersion($baseDir): string
	{
		$baseDirHash = sha1($baseDir);
		$key = $this->sid . '|base_dir|' . $baseDirHash;

		if (!isset(static::$baseDirVersion[$key]))
		{
			static::$baseDirVersion[$key] = $this->get($key);
		}

		if (static::$baseDirVersion[$key] === false || (static::$baseDirVersion[$key] == ''))
		{
			static::$baseDirVersion[$key] = sha1($baseDirHash . '|' . mt_rand() . '|' . microtime());
			$this->set($key, 0, static::$baseDirVersion[$key]);
		}

		return static::$baseDirVersion[$key];
	}

	/**
	 * Reads cache from the memcache. Returns true if key value exists, not expired, and successfully read.
	 *
	 * @param mixed &$vars Cached result.
	 * @param string $baseDir Base cache directory (usually /bitrix/cache).
	 * @param string $initDir Directory within base.
	 * @param string $filename File name.
	 * @param integer $ttl Expiration period in seconds.
	 *
	 * @return boolean
	 */
	public function read(&$vars, $baseDir, $initDir, $filename, $ttl)
	{
		$baseDirVersion = $this->getBaseDirVersion($baseDir);
		$initDirVersion = $this->getInitDirVersion($baseDir, $initDir);

		$dir = sha1($baseDirVersion . '|' . $initDirVersion);
		$key = $this->sid. '|' . $dir . '|' . $filename;

		$initListKey = $this->sid . '|' . $dir . self::BX_DIR_LIST;
		$initPartition = $this->getPartition($filename);
		$initListKeyPartition = $initListKey . $initPartition;

		if ($this->useLock)
		{
			$cachedData = $this->get($key);
			if (
				($cachedData !== null && $cachedData !== false)
				&& !$this->checkInSet($initListKey, $initPartition)
				&& !$this->checkInSet($initListKeyPartition, $filename)
			)
			{
				$this->del($key);
				return false;
			}

			if (!is_array($cachedData))
			{
				$cachedData = $this->get($key . '|old');

				if (is_array($cachedData))
				{
					$this->old = true;
				}
			}

			if (!is_array($cachedData))
			{
				return false;
			}

			if (($cachedData['expire'] < time() || $this->old) && $this->lock($key, $ttl))
			{
				return false;
			}

			$vars = $cachedData['content'];
		}
		else
		{
			$vars = $this->get($key);
			if ($vars !== null && $vars !== false)
			{
				if (
					!$this->checkInSet($initListKey, $initPartition)
					|| !$this->checkInSet($initListKeyPartition, $filename)
				)
				{
					$this->del($key);
					return false;
				}
			}
		}

		if (Cache::getShowCacheStat())
		{
			$this->read = strlen(serialize($vars));
			$this->path = $baseDir . $initDir . $filename;
		}

		return $vars !== false;
	}

	/**
	 * Puts cache into the memcache.
	 *
	 * @param mixed $vars Cached result.
	 * @param string $baseDir Base cache directory (usually /bitrix/cache).
	 * @param string $initDir Directory within base.
	 * @param string $filename File name.
	 * @param integer $ttl Expiration period in seconds.
	 *
	 * @return void
	 */
	public function write($vars, $baseDir, $initDir, $filename, $ttl)
	{
		$baseDirVersion = $this->getBaseDirVersion($baseDir);
		$initDirVersion = $this->getInitDirVersion($baseDir, $initDir);

		$dir = sha1($baseDirVersion . '|' . $initDirVersion);
		$key = $this->sid. '|' . $dir . '|' . $filename;
		$exp = $this->ttlMultiplier * (int) $ttl;

		if ($this->useLock)
		{
			$this->set($key, $exp, ['expire' => time() + $ttl, 'content' => $vars]);
			$this->del($key . '|old');
			$this->unlock($key);
		}
		else
		{
			$this->set($key, $exp, $vars);
		}

		$initListKey = $this->sid . '|' . $dir . self::BX_DIR_LIST;
		$initPartition = $this->getPartition($filename);
		$initListKeyPartition = $initListKey . $initPartition;

		$this->addToSet($initListKeyPartition, $filename);
		$this->addToSet($initListKey, $initPartition);

		if ($this->fullClean)
		{
			$baseListKey = $this->sid . '|' . $baseDirVersion . self::BX_BASE_LIST;
			$baseListKeyPartition = $this->getPartition($initListKeyPartition);
			$this->addToSet($baseListKey . $baseListKeyPartition, $initListKeyPartition);
			$this->addToSet($baseListKey, $baseListKeyPartition);
		}

		if (Cache::getShowCacheStat())
		{
			$this->written = strlen(serialize($vars));
			$this->path = $baseDir . $initDir . $filename;
		}
	}

	/**
	 * Cleans (removes) cache directory or file.
	 *
	 * @param string $baseDir Base cache directory (usually /bitrix/cache).
	 * @param string $initDir Directory within base.
	 * @param string $filename File name.
	 *
	 * @return void
	 */
	public function clean($baseDir, $initDir = false, $filename = false)
	{
		if (!self::isAvailable())
		{
			return;
		}

		$baseDirVersion = $this->getBaseDirVersion($baseDir);
		$initDirVersion = $this->getInitDirVersion($baseDir, $initDir);

		$dir = sha1($baseDirVersion . '|' . $initDirVersion);
		$initListKey = $this->sid . '|' .$dir . self::BX_DIR_LIST;

		if ($this->fullClean)
		{
			$baseListKey = $this->sid . '|' .$baseDirVersion . self::BX_BASE_LIST;
		}

		if ($filename <> '')
		{
			$key = $this->sid . '|' .$dir . '|' . $filename;
			$this->delFromSet($initListKey . $this->getPartition($filename), $filename);

			if ($this->useLock && $cachedData = $this->get($key))
			{
				$this->set($key . '|old', $this->ttlOld, $cachedData);
			}

			$this->del($key);
			if ($this->useLock)
			{
				$this->unlock($key);
			}
		}
		elseif ($initDir != '')
		{
			$keyPrefix = $this->sid . '|' .$dir . '|';
			$partitionKeys = $this->getSet($initListKey);

			// A temporary optimization solution
			if ($this->getConnectionName() != 'cache.redis')
			{
				$this->delFromSet($initListKey, $partitionKeys);
			}
			$this->del($initListKey);

			foreach ($partitionKeys as $partition)
			{
				$delKey = $initListKey . $partition;
				$this->deleteBySet($delKey, $keyPrefix);
				if ($this->fullClean)
				{
					$this->delFromSet($baseListKey . $this->getPartition($delKey), $delKey);
				}
			}
		}
		else
		{
			$baseDirKey = $this->sid . '|base_dir|' . sha1 ($baseDir);
			$this->del($baseDirKey);
			unset(static::$baseDirVersion[$baseDirKey]);

			if ($this->fullClean)
			{
				$useLock = $this->useLock;
				$this->useLock = false;

				$keyPrefix = $this->sid . '|' .$dir . '|';
				$partitionKeys = $this->getSet($baseListKey);

				foreach ($partitionKeys as $partition)
				{
					$baseListKeyPartition = $baseListKey . $partition;
					$keys = $this->getSet($baseListKeyPartition);

					foreach ($keys as $initKey)
					{
						$this->deleteBySet($initKey, $keyPrefix);
					}

					$this->del($baseListKeyPartition);
					unset($keys);
				}

				$this->del($baseListKey);
				$this->useLock = $useLock;
			}
		}
	}
}