diff --git a/common.php b/common.php index ee978b39a..4f67ebfe1 100644 --- a/common.php +++ b/common.php @@ -46,6 +46,10 @@ $di->register(new \TorrentPier\ServiceProviders\LogServiceProvider(), [ 'config.log.handlers' => $di->config->log->handlers ]); +$di->register(new \TorrentPier\ServiceProviders\CacheServiceProvider(), [ + 'config.services.cache' => $di->config->services->cache->toArray() +]); + $di->register(new \TorrentPier\ServiceProviders\DbServiceProvider, [ 'config.db' => $di->config->db->toArray() ]); @@ -520,4 +524,4 @@ else if (defined('IN_TRACKER')) dummy_exit(mt_rand(60, 2400)); } } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index 1b65fb7f1..365123ff0 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ "zendframework/zend-loader": "^2.5", "zendframework/zend-mail": "^2.5", "zendframework/zend-session": "^2.5", - "zendframework/zend-version": "^2.5" + "zendframework/zend-version": "^2.5", + "doctrine/cache": "^1.6" }, "require-dev": { "phpunit/phpunit": "^4.8" diff --git a/library/config.php b/library/config.php index ba8063d1d..3c022d81a 100644 --- a/library/config.php +++ b/library/config.php @@ -19,6 +19,15 @@ $config = [ 'tp_release_date' => '**/**/2016', 'tp_release_state' => 'ALPHA', + 'services' => [ + 'cache' => [ + 'adapter' => \TorrentPier\Cache\FileAdapter::class, + 'options' => [ + 'directory' => __DIR__ . '/../internal_data/cache', + ], + ], + ], + // Database 'db' => [ 'debug' => '{self.debug}', @@ -533,4 +542,4 @@ else } } -return $config; \ No newline at end of file +return $config; diff --git a/src/Cache/Adapter.php b/src/Cache/Adapter.php new file mode 100644 index 000000000..f9aa78b9c --- /dev/null +++ b/src/Cache/Adapter.php @@ -0,0 +1,198 @@ + $value) { + $method = 'set' . ucfirst($key); + if (!method_exists($this, $method)) { + throw new \InvalidArgumentException('Method ' . $method . ' is undefined.'); + } + + $this->{$method}($value); + } + } + + /** + * Get object provider cache. + * + * @return CacheProvider + */ + abstract public function getProvider(); + + /** + * Get type cache. + * + * @return string + */ + abstract protected function getType(); + + /** + * Prepare key. + * + * @param string $key + * @return string + */ + protected function prepareKey($key) + { + if (empty($key) || substr($key, -2) === '::') { + throw new \InvalidArgumentException('The key is not defined.'); + } + + $result = explode('::', $key, 2); + if (empty($result[1])) { + $namespace = self::DEFAULT_NAMESPACE; + $id = $result[0]; + } else { + list($namespace, $id) = $result; + } + + $this->getProvider()->setNamespace($namespace); + + return $id; + } + + /** + * Tests if an entry exists in the cache. + * + * @param string $key + * @return bool + */ + public function has($key) + { + $id = $this->prepareKey($key); + return $this->getProvider()->contains($id); + } + + /** + * Fetches an entry from the cache. + * + * @param string $key + * @param mixed|null $defaultValue + * @return mixed|null + */ + public function get($key, $defaultValue = null) + { + $id = $this->prepareKey($key); + $result = $this->getProvider()->fetch($id); + + if ($result === false) { + return $defaultValue; + } + + return $result; + } + + /** + * Returns an associative array of values for keys is found in cache. + * + * @param array $keys + * @return array|\string[] + */ + public function gets(array $keys) + { + $keys = array_map([$this, 'prepareKey'], $keys); + return $this->getProvider()->fetchMultiple([$keys]); + } + + /** + * Puts data into the cache. + * + * @param string $key + * @param mixed $data + * @param int $timeLeft + * @return bool + */ + public function set($key, $data, $timeLeft = 0) + { + $id = $this->prepareKey($key); + return $this->getProvider()->save($id, $data, $timeLeft); + } + + /** + * Returns a boolean value indicating if the operation succeeded. + * + * @param array $keysAndValues + * @param int $timeLeft + * @return bool + */ + public function sets(array $keysAndValues, $timeLeft) + { + foreach ($keysAndValues as $key => $value) { + $id = $this->prepareKey($key); + $keysAndValues[$id] = $value; + unset($keysAndValues[$key]); + } + return $this->getProvider()->saveMultiple($keysAndValues, $timeLeft); + } + + /** + * Deletes a cache entry. + * + * @param string $id + * @return bool + */ + public function delete($id) + { + $id = $this->prepareKey($id); + return $this->getProvider()->delete($id); + } + + /** + * Deletes all cache entries in the current cache namespace. + * + * @param string $namespace + * @return bool + */ + public function deleteAll($namespace = '') + { + $this->getProvider()->setNamespace($namespace); + return $this->getProvider()->deleteAll(); + } + + /** + * Flushes all cache entries, globally. + * + * @return bool + */ + public function flush() + { + return $this->getProvider()->flushAll(); + } + + /** + * Retrieves cached information from the data store. + * + * @return array + */ + public function stats() + { + $stats = $this->getProvider()->getStats(); + if (!$stats) { + $stats = []; + } + $stats['type'] = $this->getType(); + return $stats; + } +} diff --git a/src/Cache/FileAdapter.php b/src/Cache/FileAdapter.php new file mode 100644 index 000000000..ecdf352c1 --- /dev/null +++ b/src/Cache/FileAdapter.php @@ -0,0 +1,77 @@ +provider) { + $this->provider = new PhpFileCache($this->directory, $this->extension, $this->umask); + } + + return $this->provider; + } + + /** + * {@inheritdoc} + */ + protected function getType() + { + return 'Cache in php file'; + } + + /** + * Set directory path for cache. + * + * @param $directory + */ + protected function setDirectory($directory) + { + $this->directory = $directory; + } + + /** + * Set extension file. + * + * @param $extension + */ + protected function setExtension($extension) + { + $this->extension = $extension; + } + + /** + * Set umask file. + * + * @param $umask + */ + protected function setUmask($umask) + { + $this->umask = $umask; + } +} diff --git a/src/Cache/MemoryAdapter.php b/src/Cache/MemoryAdapter.php new file mode 100644 index 000000000..c9ce5c4f5 --- /dev/null +++ b/src/Cache/MemoryAdapter.php @@ -0,0 +1,103 @@ +isMemcached = extension_loaded('memcached'); + } + + /** + * {@inheritdoc} + */ + public function getProvider() + { + if (!$this->provider) { + if ($this->isMemcached) { + $this->provider = new MemcachedCache(); + $this->provider->setMemcached($this->getMemcached()); + } else { + $this->provider = new MemcacheCache(); + $this->provider->setMemcache($this->getMemcache()); + } + } + + return $this->provider; + } + + /** + * @param array $servers + */ + protected function setServers(array $servers = []) + { + $this->servers = $servers; + } + + /** + * Get memcached api. + * + * @return \Memcached + */ + private function getMemcached() + { + $mem = new \Memcached(); + foreach ($this->servers as $server) { + $mem->addServer( + isset($server['host']) ? $server['host'] : '127.0.0.1', + isset($server['port']) ? $server['port'] : 11211, + isset($server['weight']) ? $server['weight'] : 100 + ); + } + return $mem; + } + + /** + * Get memcache api. + * + * @return \Memcache + */ + private function getMemcache() + { + $mem = new \Memcache(); + foreach ($this->servers as $server) { + $mem->addserver( + isset($server['host']) ? $server['host'] : '127.0.0.1', + isset($server['port']) ? $server['port'] : 11211, + isset($server['persistent']) ? $server['persistent'] : true, + isset($server['weight']) ? $server['weight'] : 100, + isset($server['timeout']) ? $server['timeout'] : 1 + ); + } + return $mem; + } + + /** + * {@inheritdoc} + */ + protected function getType() + { + return $this->isMemcached ? 'Cache in memcached' : 'Cache in memcache'; + } +} diff --git a/src/ServiceProviders/CacheServiceProvider.php b/src/ServiceProviders/CacheServiceProvider.php new file mode 100644 index 000000000..16d8723c5 --- /dev/null +++ b/src/ServiceProviders/CacheServiceProvider.php @@ -0,0 +1,28 @@ +setOptions($setting['options']); + return $adapter; + }; + } +} diff --git a/src/ServiceProviders/CaptchaServiceProvider.php b/src/ServiceProviders/CaptchaServiceProvider.php index 5287069b2..8a143e551 100644 --- a/src/ServiceProviders/CaptchaServiceProvider.php +++ b/src/ServiceProviders/CaptchaServiceProvider.php @@ -6,13 +6,19 @@ use Pimple\Container; use Pimple\ServiceProviderInterface; use ReCaptcha\ReCaptcha; +/** + * Class CaptchaServiceProvider + * @package TorrentPier\ServiceProviders + */ class CaptchaServiceProvider implements ServiceProviderInterface { + /** + * {@inheritdoc} + */ public function register(Container $container) { $container['captcha'] = function (Container $container) { $captcha = new ReCaptcha($container['config.captcha.secret_key']); - return $captcha; }; } diff --git a/styleguide.php b/styleguide.php index 18eb86b40..0fc21d5d1 100644 --- a/styleguide.php +++ b/styleguide.php @@ -7,8 +7,20 @@ require_once __DIR__ . '/common.php'; /** @var \TorrentPier\Di $di */ $di = \TorrentPier\Di::getInstance(); -/** @var \TorrentPier\Db\Adapter $db */ -$db = $di->db; +/** @var \TorrentPier\Cache\Adapter $cache */ +$cache = $di->cache; + +//var_dump($cache); +// +$resultHas = $cache->has('key'); +$resultGet = $cache->get('key'); +$resultSet = $cache->set('key', [[1], [2]], 5); +$resultStats = $cache->stats(); + +var_dump($resultHas, $resultGet, $resultSet, $resultStats); + +///** @var \TorrentPier\Db\Adapter $db */ +//$db = $di->db; ///** @var \Symfony\Component\HttpFoundation\Request $request */ //$request = $di->request; @@ -39,26 +51,26 @@ $db = $di->db; //$log = $di->log; //$log->debug('test debug information'); -\TorrentPier\Log::info('You will see the style guide'); - -$query = function(\Zend\Db\Sql\Select $select) { - $select->columns(['id' => 'user_id', 'name' => 'username']); - $select->join(['t' => BB_TOPICS], 't.topic_poster = u.user_id', ['title' => 'topic_title']); -// $select->where(['user_id > ?' => 0]); -}; - -$users = $db->select(['u' => BB_USERS], $query)->all(); -$usersCount = $db->count(['u' => BB_USERS], $query); - -$content = $di->view->make('styleguide', [ - 'name' => $di->request->get('name', 'Admin'), - 'users' => $users, - 'users_count' => $usersCount -]); - -/** @var \Symfony\Component\HttpFoundation\Response $response */ -$response = \Symfony\Component\HttpFoundation\Response::create(); -$response->setContent($content); - -$response->prepare($di->request); -$response->send(); +//\TorrentPier\Log::info('You will see the style guide'); +// +//$query = function(\Zend\Db\Sql\Select $select) { +// $select->columns(['id' => 'user_id', 'name' => 'username']); +// $select->join(['t' => BB_TOPICS], 't.topic_poster = u.user_id', ['title' => 'topic_title']); +//// $select->where(['user_id > ?' => 0]); +//}; +// +//$users = $db->select(['u' => BB_USERS], $query)->all(); +//$usersCount = $db->count(['u' => BB_USERS], $query); +// +//$content = $di->view->make('styleguide', [ +// 'name' => $di->request->get('name', 'Admin'), +// 'users' => $users, +// 'users_count' => $usersCount +//]); +// +///** @var \Symfony\Component\HttpFoundation\Response $response */ +//$response = \Symfony\Component\HttpFoundation\Response::create(); +//$response->setContent($content); +// +//$response->prepare($di->request); +//$response->send(); diff --git a/tests/src/Cache/FileAdapterTest.php b/tests/src/Cache/FileAdapterTest.php new file mode 100644 index 000000000..0e41e29d1 --- /dev/null +++ b/tests/src/Cache/FileAdapterTest.php @@ -0,0 +1,55 @@ +adapter = new FileAdapter(); + $this->adapter->setOptions([ + 'directory' => __DIR__ . '/../../../internal_data/cache', + 'extension' => '.torrentpier.php', + 'umask' => 0644, + ]); + } + + /** + * Check provider. + */ + public function testAdapterCache() + { + self::assertInstanceOf(PhpFileCache::class, $this->adapter->getProvider()); + } + + /** + * Test type cache adapter. + */ + public function testStatsType() + { + self::assertEquals('Cache in php file', $this->adapter->stats()['type']); + } + + /** + * Check handler error for incorrect error. + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Method setIncorrectKey is undefined. + */ + public function testIncorrectSetting() + { + $this->adapter->setOptions(['incorrectKey' => 'value']); + } +} diff --git a/tests/src/Cache/MemoryAdapterTest.php b/tests/src/Cache/MemoryAdapterTest.php new file mode 100644 index 000000000..a0ba82b93 --- /dev/null +++ b/tests/src/Cache/MemoryAdapterTest.php @@ -0,0 +1,76 @@ +adapterMemcache = $this->getMockBuilder(MemoryAdapter::class) + ->setMethods(['getMemcache']) + ->disableOriginalConstructor() + ->getMock(); + + $propMemcache = new \ReflectionProperty(MemoryAdapter::class, 'isMemcached'); + $propMemcache->setAccessible(true); + $propMemcache->setValue($this->adapterMemcache, false); + + $memcacheMock = $this->getMock('\Memcache', ['getStats']); + $memcacheMock->method('getStats')->willReturn([]); + + $this->adapterMemcache + ->method('getMemcache') + ->willReturn($memcacheMock); + + // memcached + $this->adapterMemcached = $this->getMockBuilder(MemoryAdapter::class) + ->setMethods(['getMemcached']) + ->disableOriginalConstructor() + ->getMock(); + + $propMemcached = new \ReflectionProperty(MemoryAdapter::class, 'isMemcached'); + $propMemcached->setAccessible(true); + $propMemcached->setValue($this->adapterMemcached, true); + + $memcachedMock = $this->getMock('\Memcached', ['getStats', 'getServerList']); + $memcachedMock->method('getStats')->willReturn([]); + $memcachedMock->method('getServerList')->willReturn([['host' => '127.0.0.1', 'port' => 11211]]); + + $this->adapterMemcached + ->method('getMemcache') + ->willReturn($memcachedMock); + } + + /** + * Check provider for memcache. + */ + public function testAdapterCacheMemcache() + { + self::assertInstanceOf(MemcacheCache::class, $this->adapterMemcache->getProvider()); + } + + /** + * Check provider for memcached. + */ + public function testAdapterCacheMemcached() + { + self::assertInstanceOf(MemcachedCache::class, $this->adapterMemcached->getProvider()); + } +}