I just started playing with memcache(d) last night so I have a LOT to learn about it
I am wanting to know if this code is a good way of doing what it is doing or if I should be using other memcache functions
I want to show a cache version of something, if the cache does not exist then I generate the content from mysql and set it into cache then show the mysql result on the page, then next page load it will check cache and see that it is there, so it will show it.
This code seems to do the trick but there are several different memcache functions should I be using other ones to accomplish this?
<?PHP
$memcache= new Memcache();
$memcache->connect('127.0.0.1', 11211);
$rows2= $memcache->get('therows1');
if($rows2 == ''){
$myfriends = findfriend2(); // this function gets our array from mysql
$memcache->set('therows1', $myfriends, 0, 30);
echo '<pre>';
print_r($myfriends); // print the mysql version
echo '</pre>';
}else{
echo '<pre>';
print_r($rows2); //print the cached version
echo '</pre>';
}
?>
Here is the locking function provided in the link posted by #crescentfresh
<?PHP
// {{{ locked_mecache_update($memcache,$key,$updateFunction,$expiryTime,$waitUTime,$maxTries)
/**
* A function to do ensure only one thing can update a memcache at a time.
*
* Note that there are issues with the $expiryTime on memcache not being
* fine enough, but this is the best I can do. The idea behind this form
* of locking is that it takes advantage of the fact that
* {#link memcache_add()}'s are atomic in nature.
*
* It would be possible to be a more interesting limiter (say that limits
* updates to no more than 1/second) simply by storing a timestamp or
* something of that nature with the lock key (currently stores "1") and
* not deleitng the memcache entry.
*
* #package TGIFramework
* #subpackage functions
* #copyright 2009 terry chay
* #author terry chay <tychay#php.net>
* #param $memcache memcache the memcache object
* #param $key string the key to do the update on
* #param $updateFunction mixed the function to call that accepts the data
* from memcache and modifies it (use pass by reference).
* #param $expiryTime integer time in seconds to allow the key to last before
* it will expire. This should only happen if the process dies during update.
* Choose a number big enough so that $updateFunction will take much less
* time to execute.
* #param $waitUTime integer the amount of time in microseconds to wait before
* checking for the lock to release
* #param $maxTries integer maximum number of attempts before it gives up
* on the locks. Note that if $maxTries is 0, then it will RickRoll forever
* (never give up). The default number ensures that it will wait for three
* full lock cycles to crash before it gives up also.
* #return boolean success or failure
*/
function locked_memcache_update($memcache, $key, $updateFunction, $expiryTime=3, $waitUtime=101, $maxTries=100000)
{
$lock = 'lock:'.$key;
// get the lock {{{
if ($maxTries>0) {
for ($tries=0; $tries< $maxTries; ++$tries) {
if ($memcache->add($lock,1,0,$expiryTime)) { break; }
usleep($waitUtime);
}
if ($tries == $maxTries) {
// handle failure case (use exceptions and try-catch if you need to be nice)
trigger_error(sprintf('Lock failed for key: %s',$key), E_USER_NOTICE);
return false;
}
} else {
while (!$memcache->add($lock,1,0,$expiryTime)) {
usleep($waitUtime);
}
}
// }}}
// modify data in cache {{{
$data = $memcache->get($key, $flag);
call_user_func($updateFunction, $data); // update data
$memcache->set($key, $data, $flag);
// }}}
// clear the lock
$memcache->delete($lock,0);
return true;
}
// }}}
?>
Couple things.
you should be checking for false, not '' using === in the return value from get(). php's type conversions save you from doing that here, but IMHO it's better to be explicit about the value you are looking for from a cache lookup
You've got a race condition there between the empty check and where you set() the db results. From http://code.google.com/p/memcached/wiki/FAQ#Race_conditions_and_stale_data:
Remember that the process of checking
memcached, fetching SQL, and storing
into memcached, is not atomic at all!
The symptoms of this are a spike in the DB CPU when the key expires and (on a high volume site) a bunch of requests simultaneously trying to hit the db and cache the value.
You can solve it by using add() instead of get. See a more concrete example here.
Related
Is it possible to make sessions per Browser tabs?
As example a user opened 2 tabs in his browser:
Tab 1 and Tab 2
In Tab 1 he has a session:
$_SESSION['xxx'] = 'lorem';
And in Tab 2 the session is:
$_SESSION['xxx'] = 'ipsum';
Now on refresh i need to get the current session in the active tab. For example if the user refreshes Tab 2 i need to get the $_SESSION['xxx'] for tab 2 on load which is 'ipsum'. But $_SESSION['xxx'] shouldn't change on Tab 1.
Is there any option to save sessions per tab. If not what are other options to handle this issue?
Thanks for any help!
PHP stores session IDs in cookies and cookies are per client (browser), not tab. So there is no simple and easy way to do this. There are ways of doing this by creating your own session handlers, but they are more hacks than solutions and as such come with their own risks and complexity. For whatever reason you may need this, I am quite sure that there is a better architectural solution than session splitting.
I've been scouring the web for answers to this problem and have not yet found a satisfying solution. I finally pulled together something in JavaScript that sort of works.
//generate a random ID, doesn't really matter how
if(!sessionStorage.tab) {
var max = 99999999;
var min = 10000000;
sessionStorage.tab = Math.floor(Math.random() * (max - min + 1) + min);
}
//set tab_id cookie before leaving page
window.addEventListener('beforeunload', function() {
document.cookie = 'tab_id=' + sessionStorage.tab;
});
HTML5 sessionStorage is not shared between tabs, so we can store a unique tab ID there. Listening for the beforeunload event on the window tells us that we're leaving (and loading some other page). By setting a cookie before we leave, we include our value in the new request without any extra URL manipulation. To distinguish between tabs you just have to check $_COOKIE['tab_id'] on the server and store sessions values appropriately.
Do note that Firefox behaves strangely, in that triggering window.open() will create a window that shares sessionStorage with its parent, giving you two tabs with the same ID. Manually opening a blank tab and then navigating to the target URL will give you separate storage. Chrome works for me in all of my tests so far.
I realize this is probably not the right answer, or even a "good" answer, but it is an answer.
Here's my solution; we're using this to allow multiple app views open per client.
POST to get data:
'doStuff=getData&model=GTD&sn=6789&type=random&date=18-Dec-2018'
the controller then gets the data from the tables, build the object for the device, stores it to the session object and stores it in the variable as such. It generates a per tab guid so that the user can compare the same instrument with a different view in the UI.
$_session[$model][$sn][$type][$guid][$key];
the guid of course is also sent back in the data object so that the tab knows how to recall that data later on.
When the user wants to print the results to a file (pdf, etc) it sends a post with the relevant data in a POST.
'doStuff=saveFile&model=GTD&sn=6789&type=random&calDate=18-Dec-2018&guid=randomKey'
The controller then will pass that to the storage to retrieve.
Session class file example:
<?php
class Session {
public $fieldKey1;
public $fieldKey2;
public function GetStorage($model, $sn, $type, $guid, $key) {
return $_SESSION[$model][$sn][$type][$guid][$key];
}
}
?>
the controller file:
<?php
require_once('session.php');
global $session; //from session class file
switch($_POST['doStuff']) {
case 'saveFile':
$session->GetStorage($_POST['model'], $_POST['sn'], $_POST['type'], $_POST['guid'], $key);
break;
}
?>
This allows the user to have several views of the same data, without overwriting the data-set from each tab. If you don't need as much data granularity per tab, you can of course simplify the number of keys for your $_SESSION variable.
I've been taking a shot to make a web app with this feature.
It have not been matured and have constraints and flows, like having to transmit the session id in the url if your javascript make a post to an external php code depending on it, but it's functional and suits my needs (for now).
I am thinking of a more secure solution, so feel free to adapt it to your needs and give me your suggestions.
<?php
/**
* Split $_SESSION by browser Tab emulator.
* methods exemples are used whith :
* $session = new SessionSplit();
* as SessionSplit may reload the page, it has to be used on top of the code.
*
*/
class SessionSplit{
public $id;
private $gprefix="session_slice_";
private $prefix="";
private $witness="";
function SessionSplit($witness='witness'){
if(session_status()===PHP_SESSION_NONE){
session_start();
}
$this->witness=$witness;
if($this->get_id()){
$this->prefix=$this->gprefix.$this->id;
//set a witness to 'register' the session id
$this->set($this->witness,'true');
}else{
// force the session id in the url to not interfere with form validation
$actual_link = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$new_link = $actual_link.(strpos($actual_link,'?')===false?'?':'&').
'session_id='.$this->id;
header('Location: '.$new_link);
}
}
private function get_id(){
if(isset($_GET['session_id'])){
$this->id=$_GET['session_id'];
return true;
}else{
$this->new_id();
return false;
}
}
private function new_id(){
$id=0;
while(isset($_SESSION[$this->gprefix.$id.'.'.$this->witness])){$id++;}
$this->id=$id;
}
// ----------- publics
public function clearAll(){
foreach($_SESSION as $key=>$value){
if(strpos($key,$this->prefix.'.')===0){
unset($_SESSION[$key]);
}
}
}
/**
* $is_user=$session->has('user');
* equivalent to
* $is_user=isset($_SESSION['user']);
* #param {string} $local_id
* #return {boolean}
*/
public function has($local_id){
return isset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
*
* $session->clear('user');
* equivalent to
* unset($_SESSION['user']);
* #param {string} $local_id
*/
public function clear($local_id){
unset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
* $user=$session->get('user');
* equivalent to
* $user=$_SESSION['user'];
* #param {string} $local_id
* #return {mixed}
*/
public function get($local_id){
if (isset($_SESSION[$this->prefix.'.'.$local_id])) {
return $_SESSION[$this->prefix.'.'.$local_id];
}else return null;
}
/**
* $session->set('user',$user);
* equivalent to
* $_SESSION['user']=$user;
* #param {string} $local_id
* #param {mixed} $value
*/
public function set($local_id,$value){
$_SESSION[$this->prefix.'.'.$local_id]=$value;
}
};
?>
I have CouchBase server.
And have a question about concurrent document mutations: http://developer.couchbase.com/documentation/server/4.0/developer-guide/cas-concurrency.html
Code example with saving data in CouchBase:
try {
Yii::$app->Couch->set($key, $data, 0, '', 1);
} catch (\Exception $e) {
$already_saved = Yii::$app->Couch->get($key);
Yii::$app->Logger->alert(
'CouchBase exception',
[
'exception' => $e->getMessage(),
'key' => $key,
'need_saved' => $data,
'already_saved' => $already_saved,
'equal' => md5($already_saved)==md5(json_encode($data))
]
);
}
/**
* Store a document in the cluster.
*
* The set operation stores a document in the cluster. It differs from
* add and replace in that it does not care for the presence of
* the identifier in the cluster.
*
* If the $cas field is specified, set will <b>only</b> succeed if the
* identifier exists in the cluster with the <b>exact</b> same cas value
* as the one specified in this request.
*
* #param string $id the identifier to store the document under
* #param object|string $document the document to store
* #param integer $expiry the lifetime of the document (0 == infinite)
* #param string $cas a cas identifier to restrict the store operation
* #param integer $persist_to wait until the document is persisted to (at least)
* this many nodes
* #param integer $replicate_to wait until the document is replicated to (at least)
* this many nodes
* #return string the cas value of the object if success
* #throws CouchbaseException if an error occurs
*/
function set($id, $document, $expiry = 0, $cas = "", $persist_to = 0, $replicate_to = 0) {
}
But less than 0.002% from all messages I receive Exception:
CouchBase exception. The document was mutated.
Find this in documentation:
CAS is an acronym for Compare And Swap, and is known as a form of
optimistic locking. The CAS can be supplied by applications to
mutation operations ( insert, upsert, replace). When applications
provide the CAS, server will check the application-provided version of
CAS against its own version of the CAS:
If the two CAS values match (they compare successfully), then the mutation operation succeeds.
If the two CAS values differ, then the mutation operation fails
But still can't understand, what this mutation means?
Why if CAS values match, then the mutation operation succeeds, isn't it just rewrite message data?
Why if values differ, then the mutation operation fails?
Why I receive this Exception?
You can think about CAS as "revision number", which describes the document, but these "revision numbers" are not ordered, you allowed only to tell if two revisions are the same or not. For every change of the document the server will generate new CAS value (even if you rewrite the body with the same contents, set expiration time or lock the key).
So you might seen the CAS mismatch errors when document change occurred, but the content still the same, I can guess it from how you calculate md5 from the body, without CAS.
Is it possible to make sessions per Browser tabs?
As example a user opened 2 tabs in his browser:
Tab 1 and Tab 2
In Tab 1 he has a session:
$_SESSION['xxx'] = 'lorem';
And in Tab 2 the session is:
$_SESSION['xxx'] = 'ipsum';
Now on refresh i need to get the current session in the active tab. For example if the user refreshes Tab 2 i need to get the $_SESSION['xxx'] for tab 2 on load which is 'ipsum'. But $_SESSION['xxx'] shouldn't change on Tab 1.
Is there any option to save sessions per tab. If not what are other options to handle this issue?
Thanks for any help!
PHP stores session IDs in cookies and cookies are per client (browser), not tab. So there is no simple and easy way to do this. There are ways of doing this by creating your own session handlers, but they are more hacks than solutions and as such come with their own risks and complexity. For whatever reason you may need this, I am quite sure that there is a better architectural solution than session splitting.
I've been scouring the web for answers to this problem and have not yet found a satisfying solution. I finally pulled together something in JavaScript that sort of works.
//generate a random ID, doesn't really matter how
if(!sessionStorage.tab) {
var max = 99999999;
var min = 10000000;
sessionStorage.tab = Math.floor(Math.random() * (max - min + 1) + min);
}
//set tab_id cookie before leaving page
window.addEventListener('beforeunload', function() {
document.cookie = 'tab_id=' + sessionStorage.tab;
});
HTML5 sessionStorage is not shared between tabs, so we can store a unique tab ID there. Listening for the beforeunload event on the window tells us that we're leaving (and loading some other page). By setting a cookie before we leave, we include our value in the new request without any extra URL manipulation. To distinguish between tabs you just have to check $_COOKIE['tab_id'] on the server and store sessions values appropriately.
Do note that Firefox behaves strangely, in that triggering window.open() will create a window that shares sessionStorage with its parent, giving you two tabs with the same ID. Manually opening a blank tab and then navigating to the target URL will give you separate storage. Chrome works for me in all of my tests so far.
I realize this is probably not the right answer, or even a "good" answer, but it is an answer.
Here's my solution; we're using this to allow multiple app views open per client.
POST to get data:
'doStuff=getData&model=GTD&sn=6789&type=random&date=18-Dec-2018'
the controller then gets the data from the tables, build the object for the device, stores it to the session object and stores it in the variable as such. It generates a per tab guid so that the user can compare the same instrument with a different view in the UI.
$_session[$model][$sn][$type][$guid][$key];
the guid of course is also sent back in the data object so that the tab knows how to recall that data later on.
When the user wants to print the results to a file (pdf, etc) it sends a post with the relevant data in a POST.
'doStuff=saveFile&model=GTD&sn=6789&type=random&calDate=18-Dec-2018&guid=randomKey'
The controller then will pass that to the storage to retrieve.
Session class file example:
<?php
class Session {
public $fieldKey1;
public $fieldKey2;
public function GetStorage($model, $sn, $type, $guid, $key) {
return $_SESSION[$model][$sn][$type][$guid][$key];
}
}
?>
the controller file:
<?php
require_once('session.php');
global $session; //from session class file
switch($_POST['doStuff']) {
case 'saveFile':
$session->GetStorage($_POST['model'], $_POST['sn'], $_POST['type'], $_POST['guid'], $key);
break;
}
?>
This allows the user to have several views of the same data, without overwriting the data-set from each tab. If you don't need as much data granularity per tab, you can of course simplify the number of keys for your $_SESSION variable.
I've been taking a shot to make a web app with this feature.
It have not been matured and have constraints and flows, like having to transmit the session id in the url if your javascript make a post to an external php code depending on it, but it's functional and suits my needs (for now).
I am thinking of a more secure solution, so feel free to adapt it to your needs and give me your suggestions.
<?php
/**
* Split $_SESSION by browser Tab emulator.
* methods exemples are used whith :
* $session = new SessionSplit();
* as SessionSplit may reload the page, it has to be used on top of the code.
*
*/
class SessionSplit{
public $id;
private $gprefix="session_slice_";
private $prefix="";
private $witness="";
function SessionSplit($witness='witness'){
if(session_status()===PHP_SESSION_NONE){
session_start();
}
$this->witness=$witness;
if($this->get_id()){
$this->prefix=$this->gprefix.$this->id;
//set a witness to 'register' the session id
$this->set($this->witness,'true');
}else{
// force the session id in the url to not interfere with form validation
$actual_link = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$new_link = $actual_link.(strpos($actual_link,'?')===false?'?':'&').
'session_id='.$this->id;
header('Location: '.$new_link);
}
}
private function get_id(){
if(isset($_GET['session_id'])){
$this->id=$_GET['session_id'];
return true;
}else{
$this->new_id();
return false;
}
}
private function new_id(){
$id=0;
while(isset($_SESSION[$this->gprefix.$id.'.'.$this->witness])){$id++;}
$this->id=$id;
}
// ----------- publics
public function clearAll(){
foreach($_SESSION as $key=>$value){
if(strpos($key,$this->prefix.'.')===0){
unset($_SESSION[$key]);
}
}
}
/**
* $is_user=$session->has('user');
* equivalent to
* $is_user=isset($_SESSION['user']);
* #param {string} $local_id
* #return {boolean}
*/
public function has($local_id){
return isset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
*
* $session->clear('user');
* equivalent to
* unset($_SESSION['user']);
* #param {string} $local_id
*/
public function clear($local_id){
unset($_SESSION[$this->prefix.'.'.$local_id]);
}
/**
* $user=$session->get('user');
* equivalent to
* $user=$_SESSION['user'];
* #param {string} $local_id
* #return {mixed}
*/
public function get($local_id){
if (isset($_SESSION[$this->prefix.'.'.$local_id])) {
return $_SESSION[$this->prefix.'.'.$local_id];
}else return null;
}
/**
* $session->set('user',$user);
* equivalent to
* $_SESSION['user']=$user;
* #param {string} $local_id
* #param {mixed} $value
*/
public function set($local_id,$value){
$_SESSION[$this->prefix.'.'.$local_id]=$value;
}
};
?>
I am in the middle of a project witch involves syncing a webshop with an external API, i have some hooks in product update etc, and every time a value which is important for the backend API is processed i do:
/**
* localObject parameters must be the same as the ones from the API
* #param $endpoint
* #param $objectName
* #param $identifierKey
* #param $identifierValue
* #param $localObject
* #return bool
*/
private function objectHasChanged($endpoint, $objectName, $identifierKey, $identifierValue, $localObject) {
$res = $this->request("GET", "/$endpoint?$identifierKey=".$identifierValue);
if ($res->status !== 200 || !property_exists($res->body, $endpoint))
return false;
$BBObject = $res->body->{$endpoint}[0];
$objectHasChanged = false;
foreach ($BBObject as $property => $value) {
if (property_exists($localObject, $property)) {
if ($value != $localObject->{$property}) {
$BBObject->$property = $localObject->$property;
$objectHasChanged = true;
}
}
}
if ($objectHasChanged) {
$this->request("PUT", "/$endpoint/".$BBObject->id, array(
$objectName => $BBObject
));
}
return $BBObject;
}
This basically queries for a GET which on avg prob returns around 20 items, loops the items against the local copy, and updates the backend if changes are present, would it be faster to just do a PUT every time?
The PUT update also contains about 20 items on avg. The data is JSON.
If the systems are close (ie. not on veery slow lines), get is always faster than update. (not really, but bear with me).
You see, once the database starts filling up with +10.000 products, updates tends to cost alot. Especially if youre doing it on a busy shop frontend.
(Im gonna assume MySQL as backend to the webservice here).
Its a lot cheaper doing GET's that will consume 20 times the bandwidth (assuming you have the bandwidth), than UPDATE queries. Mostly because SELECT will be answered from mysql's cache. As far as i can tell from mysql documentation, UPDATE does NOT answer from cache and need to go to disk for every query and UPDATE has a lot of potential for causing a table lock while working.
This basically means, that the 20 questions. (or 200, if you need) will be answered from RAM, which is very fast.
Disk IO will probably be your limiting factor on a live shop system.
But to be honest, if i was you i'd try out them both and measure the time spend in each case. Just keep in mind that everything slows down (especially updates) when you get many products.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 years ago.
Improve this question
What are the best practices around creating flat file database structures in PHP?
A lot of more matured PHP flat file frameworks out there which I attempt to implement SQL-like query syntax which is over the top for my purposes in most cases. (I would just use a database at that point).
Are there any elegant tricks out there to get good performance and features with a small code overhead?
Well, what is the nature of the flat databases. Are they large or small. Is it simple arrays with arrays in them? if its something simple say userprofiles built as such:
$user = array("name" => "bob",
"age" => 20,
"websites" => array("example.com","bob.example.com","bob2.example.com"),
"and_one" => "more");
and to save or update the db record for that user.
$dir = "../userdata/"; //make sure to put it bellow what the server can reach.
file_put_contents($dir.$user['name'],serialize($user));
and to load the record for the user
function &get_user($name){
return unserialize(file_get_contents("../userdata/".$name));
}
but again this implementation will vary on the application and nature of the database you need.
You might consider SQLite. It's almost as simple as flat files, but you do get a SQL engine for querying. It works well with PHP too.
In my opinion, using a "Flat File Database" in the sense you're meaning (and the answer you've accepted) isn't necessarily the best way to go about things. First of all, using serialize() and unserialize() can cause MAJOR headaches if someone gets in and edits the file (they can, in fact, put arbitrary code in your "database" to be run each time.)
Personally, I'd say - why not look to the future? There have been so many times that I've had issues because I've been creating my own "proprietary" files, and the project has exploded to a point where it needs a database, and I'm thinking "you know, I wish I'd written this for a database to start with" - because the refactoring of the code takes way too much time and effort.
From this I've learnt that future proofing my application so that when it gets bigger I don't have to go and spend days refactoring is the way to go forward. How do I do this?
SQLite. It works as a database, uses SQL, and is pretty easy to change over to MySQL (especially if you're using abstracted classes for database manipulation like I do!)
In fact, especially with the "accepted answer"'s method, it can drastically cut the memory usage of your app (you don't have to load all the "RECORDS" into PHP)
One framework I'm considering would be for a blogging platform. Since just about any possible view of data you would want would be sorted by date, I was thinking about this structure:
One directory per content node:
./content/YYYYMMDDHHMMSS/
Subdirectories of each node including
/tags
/authors
/comments
As well as simple text files in the node directory for pre- and post-rendered content and the like.
This would allow a simple PHP glob() call (and probably a reversal of the result array) to query on just about anything within the content structure:
glob("content/*/tags/funny");
Would return paths including all articles tagged "funny".
Here's the code we use for Lilina:
<?php
/**
* Handler for persistent data files
*
* #author Ryan McCue <cubegames#gmail.com>
* #package Lilina
* #version 1.0
* #license http://opensource.org/licenses/gpl-license.php GNU Public License
*/
/**
* Handler for persistent data files
*
* #package Lilina
*/
class DataHandler {
/**
* Directory to store data.
*
* #since 1.0
*
* #var string
*/
protected $directory;
/**
* Constructor, duh.
*
* #since 1.0
* #uses $directory Holds the data directory, which the constructor sets.
*
* #param string $directory
*/
public function __construct($directory = null) {
if ($directory === null)
$directory = get_data_dir();
if (substr($directory, -1) != '/')
$directory .= '/';
$this->directory = (string) $directory;
}
/**
* Prepares filename and content for saving
*
* #since 1.0
* #uses $directory
* #uses put()
*
* #param string $filename Filename to save to
* #param string $content Content to save to cache
*/
public function save($filename, $content) {
$file = $this->directory . $filename;
if(!$this->put($file, $content)) {
trigger_error(get_class($this) . " error: Couldn't write to $file", E_USER_WARNING);
return false;
}
return true;
}
/**
* Saves data to file
*
* #since 1.0
* #uses $directory
*
* #param string $file Filename to save to
* #param string $data Data to save into $file
*/
protected function put($file, $data, $mode = false) {
if(file_exists($file) && file_get_contents($file) === $data) {
touch($file);
return true;
}
if(!$fp = #fopen($file, 'wb')) {
return false;
}
fwrite($fp, $data);
fclose($fp);
$this->chmod($file, $mode);
return true;
}
/**
* Change the file permissions
*
* #since 1.0
*
* #param string $file Absolute path to file
* #param integer $mode Octal mode
*/
protected function chmod($file, $mode = false){
if(!$mode)
$mode = 0644;
return #chmod($file, $mode);
}
/**
* Returns the content of the cached file if it is still valid
*
* #since 1.0
* #uses $directory
* #uses check() Check if cache file is still valid
*
* #param string $id Unique ID for content type, used to distinguish between different caches
* #return null|string Content of the cached file if valid, otherwise null
*/
public function load($filename) {
return $this->get($this->directory . $filename);
}
/**
* Returns the content of the file
*
* #since 1.0
* #uses $directory
* #uses check() Check if file is valid
*
* #param string $id Filename to load data from
* #return bool|string Content of the file if valid, otherwise null
*/
protected function get($filename) {
if(!$this->check($filename))
return null;
return file_get_contents($filename);
}
/**
* Check a file for validity
*
* Basically just a fancy alias for file_exists(), made primarily to be
* overriden.
*
* #since 1.0
* #uses $directory
*
* #param string $id Unique ID for content type, used to distinguish between different caches
* #return bool False if the cache doesn't exist or is invalid, otherwise true
*/
protected function check($filename){
return file_exists($filename);
}
/**
* Delete a file
*
* #param string $filename Unique ID
*/
public function delete($filename) {
return unlink($this->directory . $filename);
}
}
?>
It stores each entry as a separate file, which we found is efficient enough for use (no unneeded data is loaded and it's faster to save).
IMHO, you have two... er, three options if you want to avoid homebrewing something:
SQLite
If you're familiar with PDO, you can install a PDO driver that supports SQLite. Never used it, but I have used PDO a ton with MySQL. I'm going to give this a shot on a current project.
XML
Done this many times for relatively small amounts of data. XMLReader is a lightweight, read-forward, cursor-style class. SimpleXML makes it simple to read an XML document into an object that you can access just like any other class instance.
JSON (update)
Good option for smallish amounts of data, just read/write file and json_decode/json_encode. Not sure if PHP offers a structure to navigate a JSON tree without loading it all in memory though.
If you're going to use a flat file to persist data, use XML to structure the data. PHP has a built-in XML parser.
If you want a human-readable result, you can also use this type of file :
ofaurax|27|male|something|
another|24|unknown||
...
This way, you have only one file, you can debug it (and manually fix) easily, you can add fields later (at the end of each line) and the PHP code is simple (for each line, split according to |).
However, the drawbacks is that you should parse the entire file to search something (if you have millions of entry, it's not fine) and you should handle the separator in data (for example if the nick is WaR|ordz).
I have written two simple functions designed to store data in a file. You can judge for yourself if it's useful in this case.
The point is to save a php variable (if it's either an array a string or an object) to a file.
<?php
function varname(&$var) {
$oldvalue=$var;
$var='AAAAB3NzaC1yc2EAAAABIwAAAQEAqytmUAQKMOj24lAjqKJC2Gyqhbhb+DmB9eDDb8+QcFI+QOySUpYDn884rgKB6EAtoFyOZVMA6HlNj0VxMKAGE+sLTJ40rLTcieGRCeHJ/TI37e66OrjxgB+7tngKdvoG5EF9hnoGc4eTMpVUDdpAK3ykqR1FIclgk0whV7cEn/6K4697zgwwb5R2yva/zuTX+xKRqcZvyaF3Ur0Q8T+gvrAX8ktmpE18MjnA5JuGuZFZGFzQbvzCVdN52nu8i003GEFmzp0Ny57pWClKkAy3Q5P5AR2BCUwk8V0iEX3iu7J+b9pv4LRZBQkDujaAtSiAaeG2cjfzL9xIgWPf+J05IQ==';
foreach($GLOBALS as $var_name => $value) {
if ($value === 'AAAAB3NzaC1yc2EAAAABIwAAAQEAqytmUAQKMOj24lAjqKJC2Gyqhbhb+DmB9eDDb8+QcFI+QOySUpYDn884rgKB6EAtoFyOZVMA6HlNj0VxMKAGE+sLTJ40rLTcieGRCeHJ/TI37e66OrjxgB+7tngKdvoG5EF9hnoGc4eTMpVUDdpAK3ykqR1FIclgk0whV7cEn/6K4697zgwwb5R2yva/zuTX+xKRqcZvyaF3Ur0Q8T+gvrAX8ktmpE18MjnA5JuGuZFZGFzQbvzCVdN52nu8i003GEFmzp0Ny57pWClKkAy3Q5P5AR2BCUwk8V0iEX3iu7J+b9pv4LRZBQkDujaAtSiAaeG2cjfzL9xIgWPf+J05IQ==')
{
$var=$oldvalue;
return $var_name;
}
}
$var=$oldvalue;
return false;
}
function putphp(&$var, $file=false)
{
$varname=varname($var);
if(!$file)
{
$file=$varname.'.php';
}
$pathinfo=pathinfo($file);
if(file_exists($file))
{
if(is_dir($file))
{
$file=$pathinfo['dirname'].'/'.$pathinfo['basename'].'/'.$varname.'.php';
}
}
file_put_contents($file,'<?php'."\n\$".$varname.'='.var_export($var, true).";\n");
return true;
}
This one is inspiring as a practical solution:
https://github.com/mhgolkar/FlatFire
It uses multiple strategies to handling data...
[Copied from Readme File]
Free or Structured or Mixed
- STRUCTURED
Regular (table, row, column) format.
[DATABASE]
/ \
TX TableY
\_____________________________
|ROW_0 Colum_0 Colum_1 Colum_2|
|ROW_1 Colum_0 Colum_1 Colum_2|
|_____________________________|
- FREE
More creative data storing. You can store data in any structure you want for each (free) element, its similar to storing an array with a unique "Id".
[DATABASE]
/ \
EX ElementY (ID)
\________________
|Field_0 Value_0 |
|Field_1 Value_1 |
|Field_2 Value_2 |
|________________|
recall [ID]: get_free("ElementY") --> array([Field_0]=>Value_0,[Field_1]=>Value_1...
- MIXD (Mixed)
Mixed databases can store both free elements and tables.If you add a table to a free db or a free element to a structured db, flat fire will automatically convert FREE or SRCT to MIXD database.
[DATABASE]
/ \
EX TY
Just pointing out a potential problem with a flat file database with this type of system:
data|some text|more data
row 2 data|bla hbalh|more data
...etc
The problem is that the cell data contains a "|" or a "\n" then the data will be lost. Sometimes it would be easier to split by combinations of letters that most people wouldn't use.
For example:
Column splitter: #$% (Shift+345)
Row splitter: ^&* (Shift+678)
Text file: test data#$%blah blah#$%^&*new row#$%new row data 2
Then use: explode("#$%", $data); use foreach, the explode again to separate columns
Or anything along these lines. Also, I might add that flat file databases are good for systems with small amounts of data (ie. less than 20 rows), but become huge memory hogs for larger databases.