Source for file FTCache.php
Documentation is available at
FTCache.php
<?php
/**
* A collection of classes and interfaces to be used in implementing caching of objects.
*
* This system is incredibly flexible in that it allows us to use a variety of strategies
* for caching along with a variety of storage mechanisms. Strategies are basically methods
* to determine how to map objects to their cache entries. Containers allow us to customize
* where the data is stored (file system, ram, mem_cached, database, etc).
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
/**
* The center of the caching system. This handles cacheable objects and stores
* them in the chosen container mechanism using the given algorithm.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
class
FTCache
{
/**
* Algorithm we use to map object ids and cache indices
*
*
@var
FTCache_Strategy
*/
protected
$_strategy
= NULL
;
/**
* Container where the cache entries are stored
*
*
@var
FTCache_Container
*/
protected
$_container
= NULL
;
/**
* Creates a new cache with the given options.
*
*
@param
int
$size
*
@param
string
|
FTCache_Strategy
$algorithm
Name or Instance of an FTCache_Strategy class to use
*
@param
string
|
FTCache_Container
$container
Name or Instance of an FTCache_Container class to use
*
@throws
Exception Throws an exception if we have invalid parameter
*/
public
function
__construct
(
$size
,
$algorithm
,
$container
)
{
// Enforce valid size
if
(
!
is_numeric
(
$size
)
||
$size
<= 0
)
throw new Exception
(
"Invalid Size"
)
;
// If $algorithm is already an instance of strategy, then use the object
if
(
$algorithm
instanceof
FTCache_Strategy
)
$this
->
_strategy
=
$algorithm
;
// Otherwise, validate and instantiate
else
{
$algorithm
=
'FTCache_Strategy_'
.
$algorithm
;
// Enforce valid algorithm choice
if
(
!
class_exists
(
$algorithm
)
||
!
is_subclass_of
(
$algorithm
,
'FTCache_Strategy'
) )
throw new Exception
(
"Invalid Algorithm"
)
;
// Create the strategy maintainer
$this
->
_strategy
= new
$algorithm
(
$size
)
;
}
// If $container is already an instance of container, then use the object
if
(
$container
instanceof
FTCache_Container
)
$this
->
_container
=
$container
;
// Otherwise, validate and instantiate
else
{
// Rewrite some rules
$container
=
'FTCache_Container_'
.
$container
;
// Enforce valid container choice
if
(
!
class_exists
(
$container
)
||
!
is_subclass_of
(
$container
,
'FTCache_Container'
) )
throw new Exception
(
"Invalid Container"
)
;
// Create the container
$this
->
_container
= new
$container
(
$size
)
;
}
}
/**
* Retrieve the object with the given index from the cache.
*
*
@param
int
$id
Object ID
*
@param
FTCache_Cacheable
$object
Object to deserialize into
*
@return
boolean
True on success, false on error.
*/
public
function
get
(
$id
,
$object
)
{
// Make sure that the object is actually set
if
(
!
$this
->
isCached
(
$id
) )
return
false
;
// Map object id to cache index
$index
=
$this
->
_strategy
->
toCache
(
$id
)
;
// Exit on mapping error
if
(
$index
=== false
)
return
false
;
// Retrieve the data from the cache
$result
=
$this
->
_container
->
get
(
$index
)
;
// Exit on cache grab error
if
(
$result
=== false
)
return
false
;
// Otherwise, deserialize
$object
->
deserialize
(
$result
)
;
// And return success
return
true
;
}
/**
* Check if the given index is in the cache
*
*
@param
int
$id
Object ID
*
@return
boolean
True on cached, false on not cached
*/
public
function
isCached
(
$id
)
{
// Map object id to cache index
$index
=
$this
->
_strategy
->
toCache
(
$id
)
;
// Return no if we can't map it
if
(
$index
=== false
)
return
false
;
// Check the cache for the object
$result
=
$this
->
_container
->
get
(
$index
)
;
// If error, then not cached
if
(
$result
=== false
)
return
false
;
// Reverse the map to make sure that the object is the same coming out
$cached_id
=
$this
->
_strategy
->
fromCache
(
$index
)
;
// If cached entry id does not match given id, then fail
return
(
$cached_id
==
$id
)
;
}
/**
* Place the given object into the cache
*
*
@param
FTCache_Cacheable
$object
Object to cache
*
@return
boolean
True on success, false on error.
*/
public
function
set
(
FTCache_Cacheable
$object
)
{
// Get object id
$id
=
$object
->
getIndex
(
)
;
// Enforce valid object id
if
(
!
is_numeric
(
$id
) )
return
false
;
// Convert the object id to a cache index
$index
=
$this
->
_strategy
->
toCache
(
$id
)
;
// Exit on mapping error
if
(
$index
=== false
)
return
false
;
// Serialize the object
$data
=
$object
->
serialize
(
)
;
// Store it into the cache
$result
=
$this
->
_container
->
set
(
$index
,
$data
)
;
// If error, exit
if
(
$result
=== false
)
return
false
;
// Tell the strategy that it has been stored
$this
->
_strategy
->
stored
(
$object
->
getIndex
(
))
;
// Return success
return
true
;
}
}
/**
* Interface for any object that is cacheable by this caching scheme.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
interface
FTCache_Cacheable
{
/**
* Get a unique identifier for this particular object.
*
*
@return
int
Identifier
*/
public
function
getIndex
(
)
;
/**
* Convert the object to a string of data that represents its entire state.
*
*
@return
string
Serialization of object
*/
public
function
serialize
(
)
;
/**
* Convert the current object to be a the object pulled from cache.
*
*
@param
string
$data
Serialization of object
*
@return
boolean
True on success, false on error.
*/
public
function
deserialize
(
$data
)
;
}
/**
* Describes the functions that we will need to implement to provide a mapping between
* the objects ID and the ID used in the cache.
*
* NOTE: calling fromCache(toCache($some_id)) will not necessarily return $some_id! This
* is because toCache is used to map both already cached id's and not yet cached ids
* while fromCache is used to only return the id of already cached entries.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
abstract
class
FTCache_Strategy
{
/**
* Size of the schema, used for the modulus
*
*
@var
int
*/
protected
$_size
= 0
;
/**
* Sets the size of the cache.
*
*
@param
int
$size
Size of the cache
*/
public
function
__construct
(
$size
)
{
// Enforce valid size
if
(
!
is_numeric
(
$size
)
||
$size
<= 0
)
throw new Exception
(
"Invalid Size"
)
;
// Save the size
$this
->
_size
=
$size
;
}
/**
* Convert a cache entry index into an object id
*
* This method must return the object id of the cache entry stored at the given index.
* If there is nothing stored there, then it must return false.
*
*
@param
int
$index
Cache entry index
*
@return
int
Object Id. False if we can't map that index or nothing is stored there.
*/
public
abstract
function
fromCache
(
$index
)
;
/**
* Whenever the cache will store an object in the container, it informs this strategy
* object by calling this method.
*
*
@param
int
$id
Object id
*/
public
abstract
function
stored
(
$id
)
;
/**
* Convert an object ID into a cache entry index.
*
* This method must return the index that the given object id is stored at if it is
* currently stored, or the index that it will be stored at if it is not already stored.
*
*
@param
int
$id
Object ID
*
@return
int
Cache Index. False if we can't / won't map that index
*/
public
abstract
function
toCache
(
$id
)
;
}
/**
* This describes the way the functions that we will need to implement to store the given
* data into the cache.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
abstract
class
FTCache_Container
{
/**
* Size of the schema
*
*
@var
int
*/
protected
$_size
= 0
;
/**
* Sets the size of the cache
*
*
@param
int
$size
Number of entries to store in the container at once
*/
public
function
__construct
(
$size
)
{
// Enforce valid size
if
(
!
is_numeric
(
$size
)
||
$size
<= 0
)
throw new Exception
(
"Invalid Size"
)
;
// Save the size
$this
->
_size
=
$size
;
}
/**
* Retrieve the string of stored data at the given cache index
*
*
@param
int
$index
Cache index
*
@return
string
Data stored there. False if no data stored there.
*/
public
abstract
function
get
(
$index
)
;
/**
* Sets the cache entry with the given index to contain the given data
*
*
@param
int
$index
Cache index
*
@param
string
$data
Data to store there
*/
public
abstract
function
set
(
$index
,
$data
)
;
}
/**
* Implements a cache stored in an index flat file (csv format)
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
class
FTCache_Container_CSV
extends
FTCache_Container
{
/**
* The data we are storing in memory.
*
*
@var
file
*/
private
$filename
= NULL
;
/**
* Instantiate, set the size, and open the file for editting
*
*
@param
int
$size
Size of the cache in rows
*
@param
string
$filename
*/
public
function
__construct
(
$size
,
$filename
=
'cache'
,
$clear
= true
)
{
parent
::
__construct
(
$size
)
;
$mode
=
$clear
||
!
file_exists
(
$filename
)
?
'w+'
:
'r+'
;
$result
=
@
fopen
(
$filename
,
$mode
)
;
if
(
$result
=== false
)
throw new Exception
(
'Can not open file "'
.
$filename
.
'"'
)
;
fclose
(
$result
)
;
$this
->
filename
=
$filename
;
}
/**
* Parses a CSV file for data using the given handle for input.
*
*
@param
file
$handle
*/
private
function
_parse_csv
(
$handle
)
{
$data
= array
(
)
;
while
(
$row
=
fgetcsv
(
$handle
) )
{
// Make sure data is properly formatted
if
(
!
isset
(
$row
[
0
]
)
||
!
isset
(
$row
[
1
]
)
||
count
(
$row
)
< 2
)
throw new Exception
(
'Improperly formatted cache file'
)
;
if
(
!
is_numeric
(
$row
[
0
]
) )
{
exit
;
throw new Exception
(
'Invalid Index in Cache File: '
.
$row
[
0
]
)
;
}
if
(
$row
[
0
]
< 0 ||
$row
[
0
]
>=
$this
->
_size
)
throw new Exception
(
'Invalid Index in Cache File'
)
;
// Reassemble the cache
$data
[
$row
[
0
]]
=
$row
[
1
]
;
}
return
$data
;
}
/**
* Retrieve the string of stored data at the given cache index
*
*
@param
int
$index
Cache index
*
@return
string
Data stored there. False if no data stored there.
*/
public
function
get
(
$index
)
{
// Make sure entry is within bounds
if
(
$index
>=
$this
->
_size
||
$index
< 0
)
return
false
;
// If the file does not exist, then there is no data in cache
if
(
!
file_exists
(
$this
->
filename
) )
return
false
;
// Open the file for reading
$handle
=
@
fopen
(
$this
->
filename
,
'r+'
)
;
// Handle error
if
(
$handle
=== false
)
throw new Exception
(
'Can not open file "'
.
$this
->
filename
.
'" for reading'
)
;
// Lock the file for reading
if
(
!
flock
(
$handle
,
LOCK_SH
) )
throw new Exception
(
'Can not lock file "'
.
$this
->
filename
.
'" for reading'
)
;
// Read all of the data
$data
=
$this
->
_parse_csv
(
$handle
)
;
// Unlock the file
flock
(
$handle
,
LOCK_UN
)
;
// Close the file
fclose
(
$handle
)
;
// Make sure entry has been set
if
(
!
isset
(
$data
[
$index
]
) )
return
false
;
// Return value
return
$data
[
$index
]
;
}
/**
* Sets the cache entry with the given index to contain the given data
*
*
@param
int
$index
Cache index
*
@param
string
$data
Data to store there
*/
public
function
set
(
$index
,
$data
)
{
// Make sure entry is within bounds
if
(
$index
>=
$this
->
_size
||
$index
< 0
)
return
false
;
// Make sure data is a string
if
(
!
is_string
(
$data
)
&&
!
is_numeric
(
$data
) )
return
false
;
// If the file doesn't exist, then create it
if
(
!
file_exists
(
$this
->
filename
) )
if
(
!
(
$dh
=
@
fopen
(
$this
->
filename
,
'w'
)) )
throw new Exception
(
'Can not create file "'
.
$this
->
filename
.
'"'
)
;
else
fclose
(
$dh
)
;
// Open cache file
$handle
=
@
fopen
(
$this
->
filename
,
'r+'
)
;
// Handle error
if
(
$handle
=== false
)
throw new Exception
(
'Can not open file "'
.
$this
->
filename
.
'" for reading'
)
;
// Lock the resource
if
(
!
flock
(
$handle
,
LOCK_EX
) )
throw new Exception
(
'Can not lock file "'
.
$this
->
filename
.
'" for writing'
)
;
// Read the data
$cache
=
$this
->
_parse_csv
(
$handle
)
;
// Clear the file
ftruncate
(
$handle
,
0
)
;
rewind
(
$handle
)
;
// Modify the data
$cache
[
$index
]
=
$data
;
// Rewrite the data to the file
foreach
(
$cache
as
$field
=>
$content
)
fputcsv
(
$handle
,
array
(
$field
,
$content
))
;
// Release the lock
flock
(
$handle
,
LOCK_UN
)
;
// Close the file
fclose
(
$handle
)
;
// Return success
return
true
;
}
}
/**
* Implements a volatile cache that only exists for this runtime of the program.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
class
FTCache_Container_Volatile
extends
FTCache_Container
{
/**
* The data we are storing in memory.
*
*
@var
array
string
*/
private
$_cache
= array
(
)
;
/**
* Retrieve the string of stored data at the given cache index
*
*
@param
int
$index
Cache index
*
@return
string
Data stored there. False if no data stored there.
*/
public
function
get
(
$index
)
{
// Make sure entry is within bounds
if
(
$index
>=
$this
->
_size
||
$index
< 0
)
return
false
;
// Make sure entry has been set
if
(
!
isset
(
$this
->
_cache
[
$index
]
) )
return
false
;
// Return value
return
$this
->
_cache
[
$index
]
;
}
/**
* Sets the cache entry with the given index to contain the given data
*
*
@param
int
$index
Cache index
*
@param
string
$data
Data to store there
*/
public
function
set
(
$index
,
$data
)
{
// Make sure entry is within bounds
if
(
$index
>=
$this
->
_size
||
$index
< 0
)
return
false
;
// Make sure data is a string
if
(
!
is_string
(
$data
)
&&
!
is_numeric
(
$data
) )
return
false
;
// Set entry
$this
->
_cache
[
$index
]
=
$data
;
// Return success
return
true
;
}
}
/**
* Implements a Direct Mapping strategy between the given object id and the cache index
* id by doing a modulus using the size of the cache.
*
*
@author
Justin DeMaris
*
@package
FTCache
*/
class
FTCache_Strategy_DirectMapping
extends
FTCache_Strategy
{
/**
* Indexed map of the current state of the cache
*
*
@var
array
int
*/
private
$_record
= array
(
)
;
/**
* Convert a cache entry index into an object id
*
* This method must return the object id of the cache entry stored at the given index.
* If there is nothing stored there, then it must return false.
*
*
@param
int
$index
Cache entry index
*
@return
int
Object Id. False if we can't map that index or nothing is stored there.
*/
public
function
fromCache
(
$index
)
{
if
(
isset
(
$this
->
_record
[
$index
]
) )
return
$this
->
_record
[
$index
]
;
else
return
false
;
}
/**
* Whenever the cache will store an object in the container, it informs this strategy
* object by calling this method.
*
*
@param
int
$id
Object id
*/
public
function
stored
(
$id
)
{
$index
=
$this
->
toCache
(
$id
)
;
// If we can't store it, then exit
if
(
$index
=== false
)
return
false
;
// Otherwise store it
$this
->
_record
[
$index
]
=
$id
;
// Success
return
true
;
}
/**
* Convert an object ID into a cache entry index.
*
* This method must return the index that the given object id is stored at if it is
* currently stored, or the index that it will be stored at if it is not already stored.
*
*
@param
int
$id
Object ID
*
@return
int
Cache Index. False if we can't / won't map that index
*/
public
function
toCache
(
$id
)
{
// Make sure it is a valid id
if
(
!
is_numeric
(
$id
) )
return
false
;
// Only work with positive numbers
$id
=
abs
(
$id
)
;
// Return the entry id
return
$id
%
$this
->
_size
;
}
}
?>
Documentation generated on Sat, 01 Mar 2008 03:16:44 +0000 by
phpDocumentor 1.4.0