First commit

This commit is contained in:
Pierre Hubert
2016-11-19 12:08:12 +01:00
commit 990540b2b9
4706 changed files with 931207 additions and 0 deletions

View File

@ -0,0 +1,320 @@
<?php
namespace Gregwar\Cache;
/**
* A cache system based on files
*
* @author Gregwar <g.passault@gmail.com>
*/
class Cache
{
/**
* Cache directory
*/
protected $cacheDirectory;
/**
* Use a different directory as actual cache
*/
protected $actualCacheDirectory = null;
/**
* Prefix directories size
*
* For instance, if the file is helloworld.txt and the prefix size is
* 5, the cache file will be: h/e/l/l/o/helloworld.txt
*
* This is useful to avoid reaching a too large number of files into the
* cache system directories
*/
protected $prefixSize = 5;
/**
* Directory mode
*
* Allows setting of the access mode for the directories created.
*/
protected $directoryMode = 0755;
/**
* Constructs the cache system
*/
public function __construct($cacheDirectory = 'cache')
{
$this->cacheDirectory = $cacheDirectory;
}
/**
* Sets the cache directory
*
* @param $cacheDirectory the cache directory
*/
public function setCacheDirectory($cacheDirectory)
{
$this->cacheDirectory = $cacheDirectory;
return $this;
}
/**
* Gets the cache directory
*
* @return string the cache directory
*/
public function getCacheDirectory()
{
return $this->cacheDirectory;
}
/**
* Sets the actual cache directory
*/
public function setActualCacheDirectory($actualCacheDirectory = null)
{
$this->actualCacheDirectory = $actualCacheDirectory;
return $this;
}
/**
* Returns the actual cache directory
*/
public function getActualCacheDirectory()
{
return $this->actualCacheDirectory ?: $this->cacheDirectory;
}
/**
* Change the prefix size
*
* @param $prefixSize the size of the prefix directories
*/
public function setPrefixSize($prefixSize)
{
$this->prefixSize = $prefixSize;
return $this;
}
/**
* Change the directory mode
*
* @param $directoryMode the directory mode to use
*/
public function setDirectoryMode($directoryMode)
{
if (!$directoryMode) {
$directoryMode = 0755;
}
$this->directoryMode = $directoryMode;
return $this;
}
/**
* Creates a directory
*
* @param $directory, the target directory
*/
protected function mkdir($directory)
{
if (!is_dir($directory)) {
@mkdir($directory, $this->directoryMode, true);
}
}
/**
* Gets the cache file name
*
* @param $filename, the name of the cache file
* @param $actual get the actual file or the public file
* @param $mkdir, a boolean to enable/disable the construction of the
* cache file directory
*/
public function getCacheFile($filename, $actual = false, $mkdir = false)
{
$path = array();
// Getting the length of the filename before the extension
$parts = explode('.', $filename);
$len = strlen($parts[0]);
for ($i=0; $i<min($len, $this->prefixSize); $i++) {
$path[] = $filename[$i];
}
$path = implode('/', $path);
$actualDir = $this->getActualCacheDirectory() . '/' . $path;
if ($mkdir && !is_dir($actualDir)) {
mkdir($actualDir, $this->directoryMode, true);
}
$path .= '/' . $filename;
if ($actual) {
return $this->getActualCacheDirectory() . '/' . $path;
} else {
return $this->getCacheDirectory() . '/' . $path;
}
}
/**
* Checks that the cache conditions are respected
*
* @param $cacheFile the cache file
* @param $conditions an array of conditions to check
*/
protected function checkConditions($cacheFile, array $conditions = array())
{
// Implicit condition: the cache file should exist
if (!file_exists($cacheFile)) {
return false;
}
foreach ($conditions as $type => $value) {
switch ($type) {
case 'maxage':
case 'max-age':
// Return false if the file is older than $value
$age = time() - filectime($cacheFile);
if ($age > $value) {
return false;
}
break;
case 'younger-than':
case 'youngerthan':
// Return false if the file is older than the file $value, or the files $value
$check = function($filename) use ($cacheFile) {
return !file_exists($filename) || filectime($cacheFile) < filectime($filename);
};
if (!is_array($value)) {
if (!$this->isRemote($value) && $check($value)) {
return false;
}
} else {
foreach ($value as $file) {
if (!$this->isRemote($file) && $check($file)) {
return false;
}
}
}
break;
default:
throw new \Exception('Cache condition '.$type.' not supported');
}
}
return true;
}
/**
* Checks if the targt filename exists in the cache and if the conditions
* are respected
*
* @param $filename the filename
* @param $conditions the conditions to respect
*/
public function exists($filename, array $conditions = array())
{
$cacheFile = $this->getCacheFile($filename, true);
return $this->checkConditions($cacheFile, $conditions);
}
/**
* Alias for exists
*/
public function check($filename, array $conditions = array())
{
return $this->exists($filename, $conditions);
}
/**
* Write data in the cache
*/
public function set($filename, $contents = '')
{
$cacheFile = $this->getCacheFile($filename, true, true);
file_put_contents($cacheFile, $contents);
return $this;
}
/**
* Alias for set()
*/
public function write($filename, $contents = '')
{
return $this->set($filename, $contents);
}
/**
* Get data from the cache
*/
public function get($filename, array $conditions = array())
{
if ($this->exists($filename, $conditions)) {
return file_get_contents($this->getCacheFile($filename, true));
} else {
return null;
}
}
/**
* Is this URL remote?
*/
protected function isRemote($file)
{
return preg_match('/^http(s{0,1}):\/\//', $file);
}
/**
* Get or create the cache entry
*
* @param $filename the cache file name
* @param $conditions an array of conditions about expiration
* @param $function the closure to call if the file does not exists
* @param $file returns the cache file or the file contents
* @param $actual returns the actual cache file
*/
public function getOrCreate($filename, array $conditions = array(), $function, $file = false, $actual = false)
{
if (!is_callable($function)) {
throw new InvalidArgumentException('The argument $function should be callable');
}
$cacheFile = $this->getCacheFile($filename, true, true);
$data = null;
if ($this->check($filename, $conditions)) {
$data = file_get_contents($cacheFile);
} else {
if(file_exists($cacheFile)) {
unlink($cacheFile);
}
$data = call_user_func($function, $cacheFile);
// Test if the closure wrote the file or if it returned the data
if (!file_exists($cacheFile)) {
$this->set($filename, $data);
} else {
$data = file_get_contents($cacheFile);
}
}
return $file ? $this->getCacheFile($filename, $actual) : $data;
}
/**
* Alias to getOrCreate with $file = true
*/
public function getOrCreateFile($filename, array $conditions = array(), $function, $actual = false)
{
return $this->getOrCreate($filename, $conditions, $function, true, $actual);
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace Gregwar\Cache;
/**
* Garbage collect a directory, this will crawl a directory, lookng
* for files older than X days and destroy them
*
* @author Gregwar <g.passault@gmail.com>
*/
class GarbageCollect
{
/**
* Drops old files of a directory
*
* @param string $directory the name of the target directory
* @param int $days the number of days to consider a file old
* @param bool $verbose enable verbose output
*
* @return true if all the files/directories of a directory was wiped
*/
public static function dropOldFiles($directory, $days = 30, $verbose = false)
{
$allDropped = true;
$now = time();
$dir = opendir($directory);
if (!$dir) {
if ($verbose) {
echo "! Unable to open $directory\n";
}
return false;
}
while ($file = readdir($dir)) {
if ($file == '.' || $file == '..') {
continue;
}
$fullName = $directory.'/'.$file;
$old = $now-filemtime($fullName);
if (is_dir($fullName)) {
// Directories are recursively crawled
if (static::dropOldFiles($fullName, $days, $verbose)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
} else {
if ($old > (24*60*60*$days)) {
self::drop($fullName, $verbose);
} else {
$allDropped = false;
}
}
}
closedir($dir);
return $allDropped;
}
/**
* Drops a file or an empty directory
*
* @param $file the file to be removed
* @param $verbose the verbosity
*/
public static function drop($file, $verbose = false)
{
if (is_dir($file)) {
@rmdir($file);
} else {
@unlink($file);
}
if ($verbose) {
echo "> Dropping $file...\n";
}
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) <2013> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,166 @@
Cache
=====
![Build status](https://travis-ci.org/Gregwar/Cache.svg?branch=master)
This is a lightweight cache system based on file and directories.
Usage
=====
Step 1: Install it
------------------
Via composer:
```json
{
"require": {
"gregwar/cache": "1.0.*"
}
}
```
Or with a clone of the repository:
```bash
git clone https://github.com/Gregwar/Cache.git
```
Or downloading it:
* [Download .zip](https://github.com/Gregwar/Cache/archive/master.zip)
* [Download .tar.gz](https://github.com/Gregwar/Cache/archive/master.tar.gz)
Step 2: Setup the rights
------------------------
You need your PHP script to have access to the cache directory, you can for instance
create a `cache` directory with mode 777:
```
mkdir cache
chmod 777 cache
```
Step 3: Access the cache
------------------------
To access the cache, you can do like this:
```php
<?php
include('vendor/autoload.php'); // If using composer
use Gregwar\Cache\Cache;
$cache = new Cache;
$cache->setCacheDirectory('cache'); // This is the default
// If the cache exists, this will return it, else, the closure will be called
// to create this image
$data = $cache->getOrCreate('red-square.png', array(), function($filename) {
$i = imagecreatetruecolor(100, 100);
imagefill($i, 0, 0, 0xff0000);
imagepng($i, $filename);
});
header('Content-type: image/png');
echo $data;
```
This will render a red square. If the cache file (which will look like `cache/r/e/d/-/s/red-square.png')
exists, it will be read, else, the closure will be called in order to create the cache file.
API
===
You can use the following methods:
* `setCacheDirectory($directory)`: sets the cache directory (see below).
* `setActualCacheDirectory($directory)`: sets the actual cache directory (see below).
* `exists($filename, $conditions = array())`: check that the $filename file exists in the cache, checking
the conditions (see below).
* `check($filename, $conditions = array())`: alias for `exists`.
* `getCacheFile($filename, $actual = false, $mkdir = false)`: gets the cache file. If the `$actual` flag
is true, the actual cache file name will be returned (see below), if the `$mkdir` flag is true, the
cache file directories tree will be created.
* `set($filename, $contents)`: write contents to `$filename` cache file.
* `write($filename, $contents)`: alias for `set()`
* `get($filename, $conditions = array())`: if the cache file for `$filename` exists, contents will be
returned, else, `NULL` will be returned.
* `setPrefixSize($prefixSize)`: sets the prefix size for directories, default is 5. For instance, the
cache file for `helloworld.txt`, will be `'h/e/l/l/o/helloworld.txt`.
* `setDirectoryMode($directoryMode)`: sets the directory mode when creating direcotries, default is `0755`.
Does not affect any directories previously created.
* `getOrCreate($filename, $conditions = array(), $function, $file = false)`: this will check if the `$filename`
cache file exists and verifies `$conditions` (see below). If the cache file is OK, it will return its
contents. Else, it will call the `$function`, passing it the target file, this function can write the
file given in parameter or just return data. Then, cache data will be returned. If `$file` flag is set,
the cache file name will be returned instead of file data.
Note: consider using an hash for the `$filename` cache file, to avoid special characters.
Conditions
==========
You can use conditions to manage file expirations on the cache, there is two way of expiring:
* Using `max-age`, in seconds, to set the maximum age of the file
* Using `younger-than`, by passing another file, this will compare the modification date
and regenerate the cache if the given file is younger.
For instance, if you want to uppercase a file:
```php
<?php
use Gregwar\Cache\Cache;
$cache = new Cache;
$data = $cache->getOrCreate('uppercase.txt',
array(
'younger-than' => 'original.txt'
),
function() {
echo "Generating file...\n";
return strtoupper(file_get_contents('original.txt'));
});
echo $data;
```
This will be create the `uppercase.txt` cache file by uppercasing the `original.txt` if the cache file
does not exists or if the `original.txt` file is more recent than the cache file.
For instance:
```
php uppercase.php # Will generate the cache file
php uppercase.php # Will not generate the cache file
touch original.txt # Sets the last modification time to now
php uppercase.php # Will re-generate the cache file
```
Cache directory and actual cache directory
==========================================
In some cases, you'll want to get the cache file name. For instance, if you're caching
images, you'll want to give a string like `cache/s/o/m/e/i/someimage.png` to put it into
an `<img>` tag. This can be done by passing the `$file` argument to the `getOrCreate` to true,
or directly using `getCacheFile` method (see above).
However, the visible `cache` directory of your users is not the same as the absolute path
you want to access. To do that, you can set both the cache directory and the actual cache directory.
The cache directory is the prefix visible by the users (for instance: `cache/s/o/m/e/i/someimage.png`),
and the actual cache directory is the prefix to use to actually access to the image (for instance:
`/var/www/somesite/cache/s/o/m/e/i/someimage.png`). This way, the file will be accessed using absolute
path and the cache file returned will directly be usable for your user's browsers.
License
=======
This repository is under the MIT license, have a look at the `LICENCE` file.

View File

@ -0,0 +1,16 @@
<?php
/**
* Registers an autoload for all the classes in Gregwar\Cache
*/
spl_autoload_register(function ($className) {
$namespace = 'Gregwar\\Cache';
if (strpos($className, $namespace) === 0) {
$className = str_replace($namespace, '', $className);
$fileName = __DIR__ . '/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($fileName)) {
require($fileName);
}
}
});