diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 412eeda..0000000 --- a/.gitattributes +++ /dev/null @@ -1,22 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.cs diff=csharp -*.sln merge=union -*.csproj merge=union -*.vbproj merge=union -*.fsproj merge=union -*.dbproj merge=union - -# Standard to msysgit -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index b9d6bd9..3b726a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,215 +1,3 @@ -################# -## Eclipse -################# - -*.pydevproject -.project -.metadata -bin/ tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.classpath -.settings/ -.loadpath - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - - -################# -## Visual Studio -################# - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results - -[Dd]ebug/ -[Rr]elease/ -x64/ -build/ -[Bb]in/ -[Oo]bj/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.log -*.scc - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -*.ncrunch* -.*crunch*.local.xml - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.Publish.xml -*.pubxml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ - -# Windows Azure Build Output -csx -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.[Pp]ublish.xml -*.pfx -*.publishsettings - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - -############# -## Windows detritus -############# - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac crap -.DS_Store - - -############# -## Python -############# - -*.py[co] - -# Packages -*.egg -*.egg-info -dist/ -build/ -eggs/ -parts/ -var/ -sdist/ -develop-eggs/ -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg +vendor/ +composer.lock diff --git a/README.md b/README.md index 394df8e..5c5f063 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,59 @@ -PHP Cache Class (File base) +PHP FileCache Class (File base) ============================ A simple file based cache based from Erik Giberti's FileCache class. See [here](http://af-design.com/blog/2010/07/30/simple-file-based-caching-in-php/) ## Enhanced Features + * Data is serialized and JSON encoded * Cache data is encrypted by `mcrypt` * File Based Cache was explained [here](http://af-design.com/blog/2010/07/30/simple-file-based-caching-in-php/) ## Installation + +Run the following command in your command line shell in your php project + +```sh +$ composer require rothkj1022/php-cache-class +``` + +Done. + +You may also edit composer.json manually then perform ```composer update```: + +``` +"require": { + "rothkj1022/php-cache-class": "^2.1.0" +} +``` + +## Getting started + +### Example usage with composer + +```php +//load composer packages +require('vendor/autoload.php'); + +//create new instance of the class +use rothkj1022\FileCache; +$cache = new FileCache\FileCache("tmp/"); +``` + +### Example usage without composer + ```php //require the class -require_once("lib/class.cache.php"); +require_once("lib/FileCache.php"); //create new instance of the class -$cache = new Cache("tmp/"); +use rothkj1022\FileCache; +$cache = new FileCache\FileCache("tmp/"); //... ``` -## Sample Call +### Local file source example + ```php $cache_key = "client_list"; @@ -29,22 +64,33 @@ if (!$clients_data = $cache->get($cache_key)) { //set the cache up! $expire = 3600; //1 hour - $cache->set($cache_key, $clients_data, $expire); + $cache->set($cache_key, $clients_data, $expire); } var_dump($clients_data); ``` +### External http GET request example +```php +$uri = 'https://raw.githubusercontent.com/bahamas10/css-color-names/master/css-color-names.json'; +$remote_data = $cache->file_get_contents($uri); +var_dump($remote_data); +``` + ## Reference + Code reference for you to get started! ### Properties + * `protected $root = '/tmp/';` - Value is pre-pended to the cache, should be the full path to the directory. * `protected $error = null;` - For holding any error messages that may have been raised * `private $_encryption_key = 'Fil3C@ch33ncryptionK3y'` - Main key used for encryption (you need to set this up inside the class) ### Methods + #### Public Methods + * `Cache::get($key)` - Reads the data from the cache specified by the cache key * `Cache::set($key [, $data, $ttl])` - Saves data to the cache. Anything that evaluates to false, null, '', boolean false, 0 will not be saved. `$ttl` Specifies the expiry time * `Cache::delete($key)` - Deletes the cache specified by the `$key` @@ -52,14 +98,35 @@ Code reference for you to get started! * `Cache::have_error()` - Can be used to inspect internal error #### Private Methods -See code to see all private methods used like `Cahce::_encrypt($pure_string)` etc. -## Feedback -All bugs, feature requests, pull requests, feedback, etc., are welcome. Visit my site at [www.lodev09.com](http://www.lodev09.com "www.lodev09.com") or email me at [lodev09@gmail.com](mailto:lodev09@gmail.com) +See code to see all private methods used like `Cache::_encrypt($pure_string)` etc. + +## Changelog + +### Version 2.1.3 + +* Fixed: Stopped echoing guzzle request errors to screen + +### Version 2.1.2 + +* Integrated guzzle for more efficient http get requests + +### Version 2.1.1 + +* Changed: Renamed class back to Erik Giberti's original name, FileCache + +### Version 2.1.0 + +* Added: Composer integration +* Added: changelog ## Credits -© 2011-2014 - Coded by Jovanni Lo / [@lodev09](http://twitter.com/lodev09) + +2010 - Authored by Erik Giberti +2011-2014 - Rewritten by Jovanni Lo / [@lodev09](https://twitter.com/lodev09) +2018 - Modified by Kevin Roth / [@rothkj1022](https://twitter.com/rothkj1022) ## License + Released under the [MIT License](http://opensource.org/licenses/MIT). See [LICENSE](LICENSE) file. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cbe65ff --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "rothkj1022/php-cache-class", + "description": "A simple file based cache based from Erik Giberti's FileCache class, forked from lodev09/php-cache-class.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Erik Giberti" + }, + { + "name": "Jovanni Lo", + "email": "lodev09@gmail.com", + "homepage": "http://www.lodev09.com/" + }, + { + "name": "Kevin Roth", + "homepage": "https://kevinroth.com/" + } + ], + "require": { + "php": "^5.2 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^7.2" + }, + "autoload": { + "psr-4": { + "rothkj1022\\FileCache\\": "lib" + } + } +} diff --git a/example.php b/example.php new file mode 100644 index 0000000..60c80a9 --- /dev/null +++ b/example.php @@ -0,0 +1,28 @@ +get($cache_key)) { + //nope. Let's get the real one then! + $clients_data = json_decode(file_get_contents("clients.json")); + + //set the cache up! + $expire = 3600; //1 hour + $cache->set($cache_key, $clients_data, $expire); +} +var_dump($clients_data); + +//get external url, and cache the contents +$uri = 'https://raw.githubusercontent.com/bahamas10/css-color-names/master/css-color-names.json'; +$remote_data = $cache->file_get_contents($uri); +var_dump($remote_data); diff --git a/index.php b/index.php deleted file mode 100644 index 9e00fec..0000000 --- a/index.php +++ /dev/null @@ -1,22 +0,0 @@ -get($cache_key)) { - //nope. Let's get the real one then! - $clients_data = json_decode(file_get_contents("clients.json")); - - //set the cache up! - $expire = 3600; //1 hour - $cache->set($cache_key, $clients_data, $expire); -} - -var_dump($clients_data); -?> diff --git a/lib/FileCache.php b/lib/FileCache.php new file mode 100644 index 0000000..c033685 --- /dev/null +++ b/lib/FileCache.php @@ -0,0 +1,341 @@ +get('sampledata'); + * if(!$data){ + * $data = array('a'=>1,'b'=>2,'c'=>3); + * $cache->set('sampledata', $data, 3600); + * } + * print $data['a']; + * + */ + +class FileCache { + + /** + * Value is pre-pended to the cache, should be the full path to the directory + * @var string + */ + protected $root = '/tmp/'; + + /** + * For holding any error messages that may have been raised + * @var string + */ + protected $error = null; + + /** + * The encryption method. This is private! set this inside this class + * @var string + */ + private $_encryption_method = 'aes-256-cbc'; + + /** + * The encryption key. Must be 32 characters. This is private! set this inside this class + * @var string + */ + private $_encryption_key = 'Z7w@L!r8&1Tgl*KcfD^ViB@xaHYE!sQ@'; + + /** + * @param string $root The root of the file cache. + */ + function __construct($root = '/tmp/') { + $this->root = $root; + // Requires the native JSON library + if (!function_exists('json_decode') || !function_exists('json_encode')) { + throw new Exception('FileCache needs the JSON PHP extensions.'); + } + } + + /** + * Saves data to the cache. Anything that evaluates to false, null, '', boolean false, 0 will + * not be saved. + * @param string $key An identifier for the data + * @param mixed $data The data to save + * @param int $ttl Seconds to store the data + * @returns boolean True if the save was successful, false if it failed + */ + public function set($key, $data = false, $ttl = 3600) { + if (!$key) { + $this->error = "Invalid key"; + return false; + } + if (!$data) { + $this->error = "Invalid data"; + return false; + } + + $key = $this->_make_file_key($key); + $store = array( + 'data' => serialize($data), + 'ttl' => time() + $ttl, + ); + $status = false; + try { + $fh = fopen($key, "w+"); + if (flock($fh, LOCK_EX)) { + ftruncate($fh, 0); + fwrite($fh, $this->_encrypt(json_encode($store))); + flock($fh, LOCK_UN); + $status = true; + } + fclose($fh); + } + catch (exception $e) { + $this->error = "Exception caught: ".$e->getMessage(); + return false; + } + return $status; + } + + /** + * Reads the data from the cache + * @param string $key An identifier for the data + * @returns mixed Data that was stored + */ + public function get($key) { + if (!$key) { + $this->error = "Invalid key"; + return false; + } + + $key = $this->_make_file_key($key); + $file_content = null; + + if (file_exists($key) !== true) { + return false; + } + + // Get the data from the file + try { + $fh = fopen($key, "r"); + if (flock($fh, LOCK_SH)) { + $file_content = trim($this->_decrypt(fread($fh, filesize($key)))); + } + fclose($fh); + } + catch (exception $e) { + $this->error = "Exception caught: ".$e->getMessage(); + return false; + } + + // Assuming we got something back... + if ($file_content) { + $store = json_decode($file_content, true); + if ($store['ttl'] < time()) { + @unlink($key); // remove the file + $this->error = "Data expired"; + return false; + } else return unserialize($store['data']); + } else return false; + } + + /** + * Remove a key, regardless of it's expire time + * @param string $key An identifier for the data + */ + public function delete($key) { + if (!$key) { + $this->error = "Invalid key"; + return false; + } + + $key = $this->_make_file_key($key); + + try { + unlink($key); // remove the file + } + catch (exception $e) { + $this->error = "Exception caught: ".$e->getMessage(); + return false; + } + + return true; + } + + /** + * Reads and clears the internal error + * @returns string Text of the error raised by the last process + */ + public function get_error() { + $message = $this->error; + $this->error = null; + return $message; + } + + /** + * Can be used to inspect internal error + * @returns boolean True if we have an error, false if we don't + */ + public function have_error() { + return ($this->error !== null) ? true : false; + } + + /** + * returns an encrypted string + * @param string $pure_string source string to encrypt + * @return string decrypted string + */ + private function _encrypt($pure_string) { + if (phpversion() < 7.1) { + $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); + $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); + $encrypted_string = mcrypt_encrypt(MCRYPT_BLOWFISH, $this->_encryption_key, utf8_encode($pure_string), MCRYPT_MODE_ECB, $iv); + } else { + //found here: https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong + if (mb_strlen($this->_encryption_key, '8bit') !== 32) { + throw new Exception("Needs a 256-bit key!"); + } + $iv_size = openssl_cipher_iv_length($this->_encryption_method); + $iv = openssl_random_pseudo_bytes($iv_size); + $ciphertext = openssl_encrypt($pure_string, $this->_encryption_method, $this->_encryption_key, OPENSSL_RAW_DATA, $iv); + $encrypted_string = $iv.$ciphertext; + } + return $encrypted_string; + } + + /** + * returns a decrypted string + * @param string $encrypted_string ecrypted string + * @return string decrypted string + */ + private function _decrypt($encrypted_string) { + if (phpversion() < 7.1) { + $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); + $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); + $decrypted_string = mcrypt_decrypt(MCRYPT_BLOWFISH, $this->_encryption_key, $encrypted_string, MCRYPT_MODE_ECB, $iv); + } else { + //found here: https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong + if (mb_strlen($this->_encryption_key, '8bit') !== 32) { + throw new Exception("Needs a 256-bit key!"); + } + $iv_size = openssl_cipher_iv_length($this->_encryption_method); + $iv = mb_substr($encrypted_string, 0, $iv_size, '8bit'); + $ciphertext = mb_substr($encrypted_string, $iv_size, null, '8bit'); + + $decrypted_string = openssl_decrypt($ciphertext, $this->_encryption_method, $this->_encryption_key, OPENSSL_RAW_DATA, $iv); + } + return $decrypted_string; + } + + /** + * Create a key for the cache + * @todo Beef up the cleansing of the file. + * @param string $key The key to create + * @returns string The full path and filename to access + */ + private function _make_file_key($key) { + $safe_key = str_replace(array( + '.', + '/', + ':', + '\''), array( + '_', + '-', + '-', + '-'), trim($key)); + return $this->root.$safe_key.".cache"; + } + + /** + * KJR 11/7/2016 - get file or url contents from given path + * @param string $uri The uri of the data we are fetching + * @param int $ttl The amount of time in seconds before cache should expire + * @returns the data or false on failure + */ + public function file_get_contents($uri, $ttl = 3600) { + $cacheFile = md5($uri); + if (!$data = $this->get($cacheFile)) { + // cache did not exist + $data = ((is_file($uri)) ? file_get_contents($uri) : $this->file_get_contents_remote($uri)); + if ($data) { + //got the data, store it + if (!$this->set($cacheFile, $data, $ttl)) { + return false; + } + } else { + return false; + } + + } + return $data; + } + + // get remote file contents + private function file_get_contents_remote($uri) { + //use guzzle to handle request + $client = new \GuzzleHttp\Client(); + $requestOptions = [ 'verify' => false ]; // accommodates self-signed certs + + try { + $response = $client->request('GET', $uri, $requestOptions); + if (in_array($response->getStatusCode(), array(200, 206))) { + $body = $response->getBody(); + return (string)$body; + } + } catch (\Exception $e) { + $exceptionType = get_class($e); + $errorMsg = $exceptionType; + $request = $e->getRequest(); + $requestMsg = \GuzzleHttp\Psr7\str($request); + //return 'Error getting content from: '.$uri.'. Request: '.$requestMsg; + } + return false; + } + + /** + * KJR 11/7/2016 - get data we know is stored as JSON and decode it + * @param string $uri The uri of the json data we are fetching + * @param int $ttl The amount of time in seconds before cache should expire + * @returns the data or false on failure + */ + public function getJsonData($jsonUri, $ttl = 3600) { + if ($jsonData = $this->file_get_contents($jsonUri, $ttl)) { + //return decoded data + return json_decode($jsonData, true); + } + return false; + } + +} diff --git a/lib/class.cache.php b/lib/class.cache.php deleted file mode 100644 index afdc135..0000000 --- a/lib/class.cache.php +++ /dev/null @@ -1,251 +0,0 @@ -get('sampledata'); - * if(!$data){ - * $data = array('a'=>1,'b'=>2,'c'=>3); - * $cache->set('sampledata', $data, 3600); - * } - * print $data['a']; - * - */ - -class Cache { - - /** - * Value is pre-pended to the cache, should be the full path to the directory - * @var string - */ - protected $root = '/tmp/'; - - /** - * For holding any error messages that may have been raised - * @var string - */ - protected $error = null; - - /** - * The encryption key. This is private! set this inside this class - * @var string - */ - private $_encryption_key = "Fil3C@ch33ncryptionK3y"; - - /** - * @param string $root The root of the file cache. - */ - function __construct($root = '/tmp/') { - $this->root = $root; - // Requires the native JSON library - if (!function_exists('json_decode') || !function_exists('json_encode')) { - throw new Exception('Cache needs the JSON PHP extensions.'); - } - } - - /** - * Saves data to the cache. Anything that evaluates to false, null, '', boolean false, 0 will - * not be saved. - * @param string $key An identifier for the data - * @param mixed $data The data to save - * @param int $ttl Seconds to store the data - * @returns boolean True if the save was successful, false if it failed - */ - public function set($key, $data = false, $ttl = 3600) { - if (!$key) { - $this->error = "Invalid key"; - return false; - } - if (!$data) { - $this->error = "Invalid data"; - return false; - } - - $key = $this->_make_file_key($key); - $store = array( - 'data' => serialize($data), - 'ttl' => time() + $ttl, - ); - $status = false; - try { - $fh = fopen($key, "w+"); - if (flock($fh, LOCK_EX)) { - ftruncate($fh, 0); - fwrite($fh, $this->_encrypt(json_encode($store))); - flock($fh, LOCK_UN); - $status = true; - } - fclose($fh); - } - catch (exception $e) { - $this->error = "Exception caught: ".$e->getMessage(); - return false; - } - return $status; - } - - /** - * Reads the data from the cache - * @param string $key An identifier for the data - * @returns mixed Data that was stored - */ - public function get($key) { - if (!$key) { - $this->error = "Invalid key"; - return false; - } - - $key = $this->_make_file_key($key); - $file_content = null; - - if (file_exists($key) !== true) { - return false; - } - - // Get the data from the file - try { - $fh = fopen($key, "r"); - if (flock($fh, LOCK_SH)) { - $file_content = trim($this->_decrypt(fread($fh, filesize($key)))); - } - fclose($fh); - } - catch (exception $e) { - $this->error = "Exception caught: ".$e->getMessage(); - return false; - } - - // Assuming we got something back... - if ($file_content) { - $store = json_decode($file_content, true); - if ($store['ttl'] < time()) { - unlink($key); // remove the file - $this->error = "Data expired"; - return false; - } else return unserialize($store['data']); - } else return false; - } - - /** - * Remove a key, regardless of it's expire time - * @param string $key An identifier for the data - */ - public function delete($key) { - if (!$key) { - $this->error = "Invalid key"; - return false; - } - - $key = $this->_make_file_key($key); - - try { - unlink($key); // remove the file - } - catch (exception $e) { - $this->error = "Exception caught: ".$e->getMessage(); - return false; - } - - return true; - } - - /** - * Reads and clears the internal error - * @returns string Text of the error raised by the last process - */ - public function get_error() { - $message = $this->error; - $this->error = null; - return $message; - } - - /** - * Can be used to inspect internal error - * @returns boolean True if we have an error, false if we don't - */ - public function have_error() { - return ($this->error !== null) ? true : false; - } - - /** - * returns an encrypted string - * @param string $pure_string source string to encrypt - * @return string decrypted string - */ - private function _encrypt($pure_string) { - $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); - $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - $encrypted_string = mcrypt_encrypt(MCRYPT_BLOWFISH, $this->_encryption_key, utf8_encode($pure_string), - MCRYPT_MODE_ECB, $iv); - return $encrypted_string; - } - - /** - * returns a decrypted string - * @param string $encrypted_string ecrypted string - * @return string decrypted string - */ - private function _decrypt($encrypted_string) { - $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); - $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); - $decrypted_string = mcrypt_decrypt(MCRYPT_BLOWFISH, $this->_encryption_key, $encrypted_string, - MCRYPT_MODE_ECB, $iv); - return $decrypted_string; - } - - /** - * Create a key for the cache - * @todo Beef up the cleansing of the file. - * @param string $key The key to create - * @returns string The full path and filename to access - */ - private function _make_file_key($key) { - $safe_key = str_replace(array( - '.', - '/', - ':', - '\''), array( - '_', - '-', - '-', - '-'), trim($key)); - return $this->root.$safe_key.".cache"; - } -} -?> \ No newline at end of file diff --git a/tmp/index.html b/tmp/index.html new file mode 100644 index 0000000..b702fbc --- /dev/null +++ b/tmp/index.html @@ -0,0 +1,11 @@ + + +
+Directory access is forbidden.
+ + +