I am working on a small class that will allow me to write queue data to a file. Similar idea to PHP $_SESSION.
I thought the following steps will do the trick
Open a file using fopen() in 'a+' mode
Lock the file using flock() with LOCK_EX type to prevent another process from using the same file
Read the file's existing content using fread(). Then put the data into array using json_decode(array, true)
Now the data is in array. If the key exists in the array update its value, otherwise insert the key to the array.
After create an array with all the data that need to go on the file, I truncate the file using ftruncate() and write the new data to the file using fwrite()
Unlock the file using flock() with LOCK_UN type to allow another process to use it.
Finally Close the file
I believe I wrote the code to satisfy the above step in my updateCache() method. But it does not seems to work properly. It does not keep track of the data.
Here is my class
<?php
namespace ICWS;
use \ICWS\showVar;
use \DirectoryIterator;
/**
* CacheHandler
*
* #package ICWS
*/
class CacheHandler {
/* Max time second allowed to keep a session File */
private $maxTimeAllowed = 300;
private $filename = '';
private $handle;
public function __construct( $filename )
{
$this->filename = $filename;
// 5% chance to collect garbage when the class is initialized.
if(mt_rand(1, 100) <= 5){
$this->collectGarbage();
}
}
private function print_me($a)
{
echo '<pre>';
print_r($a);
echo '</pre>';
}
/**
* Add/Update the cached array in the session
*
* #param string $key
* #param bigint $id
* #param array $field
* #return void
*/
public function updateCache($key, $id, array $field)
{
$currentVal = $field;
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
//create a new if the $id does not exists in the cache
if( isset($storage[$key]) && array_key_exists($id, $storage[$key]) ){
$currentVal = $storage[$key][$id];
foreach($field as $k => $v){
$currentVal[$k] = $v; //update existing key or add a new one
}
}
$storage[$key][$id] = $currentVal;
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
}
/**
* gets the current cache/session for a giving $key
*
* #param string $key. If null is passed it will return everything in the cache
* #return object or boolean
*/
public function getCache($key = null)
{
$value = false;
$this->openFile();
rewind($this->handle);
$storage = $this->readFile();
if(!is_null($key) && isset($storage[$key])){
$value = $storage[$key];
}
if(is_null($key)){
$value = $storage;
}
$this->closeFile();
return $value;
}
/**
* removes the $id from the cache/session
*
* #param string $key
* #param bigint $id
* #return boolean
*/
public function removeCache($key, $id)
{
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
if( !isset($storage[$key][$id])){
$this->unlockFile();
$this->closeFile();
return false;
}
unset($storage[$key][$id]);
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
return true;
}
/**
* unset a session
*
* #param argument $keys
* #return void
*/
public function truncateCache()
{
if(file_exists($this->filename)){
unlink($this->filename);
}
}
/**
* Open a file in a giving mode
*
* #params string $mode
* #return void
*/
private function openFile( $mode = "a+")
{
$this->handle = fopen($this->filename, $mode);
if(!$this->handle){
throw new exception('The File could not be opened!');
}
}
/**
* Close the file
* #return void
*/
private function closeFile()
{
fclose($this->handle);
$this->handle = null;
}
/**
* Update the file with array data
*
* #param array $data the array in that has the data
* #return void
*/
private function updateFile(array $data = array() )
{
$raw = json_encode($data);
$this->truncateFile();
fwrite($this->handle, $raw);
}
/**
* Delete the file content;
*
* #return void
*/
private function truncateFile( )
{
ftruncate($this->handle, 0);
rewind($this->handle);
}
/**
* Read the data from the opned file
*
* #params string $mode
* #return array of the data found in the file
*/
private function readFile()
{
$length = filesize($this->filename);
if($length > 0){
rewind($this->handle);
$raw = fread($this->handle, $length);
return json_decode($raw, true);
}
return array();
}
/**
* Lock the file
*
* #return void
*/
private function lockFile( $maxAttempts = 100, $lockType = LOCK_EX)
{
$i = 0;
while($i <= $maxAttempts){
// acquire an exclusive lock
if( flock($this->handle, LOCK_EX) ){
break;
}
++$i;
}
}
/**
* Unlock the file
*
* #return void
*/
private function unlockFile()
{
fflush($this->handle);
flock($this->handle, LOCK_UN);
}
/**
* Remove add cache files
*
* #return void
*/
private function collectGarbage()
{
$mydir = dirname($this->filename);
$dir = new DirectoryIterator( $mydir );
$time = strtotime("now");
foreach ($dir as $file) {
if ( !$file->isDot()
&& $file->isFile()
&& ($time - $file->getATime()) > $this->maxTimeAllowed
&& $file->getFilename() != 'index.html'
) {
unlink($file->getPathName());
}
}
}
function addSlashes($str)
{
return addslashes($str);
}
}
This is how I use it
<?php
require 'autoloader.php';
try {
$cache = new \ICWS\CacheHandler('cache/879');
$field = array('name' => 'Mike A', 'Address' => '123 S Main', 'phone' => '2152456245', 'ext' => 123);
$cache->updateCache('NewKey', 'first', $field);
$cache->updateCache('NewKey', 'second', $field);
$field = array('Address' => '987 S Main', 'phone' => '000000000000', 'ext' => 5555);
$cache->updateCache('NewKey', 'first', $field);
$field = array('locations' => array('S' => 'South', 'N' => 'North', 'E' => 'East'));
$cache->updateCache('NewKey', 'first', $field);
echo '<pre>';
print_r($cache->getCache());
echo '</pre>';
} catch (exception $e){
echo $e->getMessage();
}
?>
I am expecting the array to look like this
Array
(
[first] => stdClass Object
(
[name] => Mike A
[Address] => 987 S Main
[phone] => 000000000000
[ext] => 5555
[locations] => Array
(
[S] => South
[N] => North
[E] => East
)
)
[second] => stdClass Object
(
[name] => Mike A
[Address] => 123 S Main
[phone] => 2152456245
[ext] => 123
)
)
But after executing the first time, I get blank array. Then after I execute the script again I get the following array.
Array
(
[NewKey] => Array
(
[first] => Array
(
[locations] => Array
(
[S] => South
[N] => North
[E] => East
)
)
)
)
What could be causing the data not to update correctly?
I wrote my updateCache() using $_SESSION which gave me the correct output but I need to do this without sessions
Here is my sessions bases method
public function updateCache($key, $id, array $field)
{
$currentVal = (object) $field;
//create a new if the $id does not exists in the cache
if( isset($_SESSION[$key]) && array_key_exists($id, $_SESSION[$key]) ){
$currentVal = (object) $_SESSION[$key][$id];
foreach($field as $k => $v){
$currentVal->$k = $v; //update existing key or add a new one
}
}
$_SESSION[$key][$id] = $currentVal;
}
The problem lies here:
private function readFile()
{
$length = filesize($this->filename);
if($length > 0){
rewind($this->handle);
$raw = fread($this->handle, $length);
return json_decode($raw, true);
}
return array();
}
The documentation of filesize() states:
Note: The results of this function are cached. See clearstatcache() for more details.
Because the file is empty when you start, it would have cached that information and your branch is skipped entirely until the next script execution. This should fix it:
private function readFile()
{
rewind($this->handle);
$raw = stream_get_contents($this->handle);
return json_decode($raw, true) ?: [];
}
Related
Text File 1:
426684146543xxxx|xx|xxxx|xxx
407166210197xxxx|xx|xxxx|xxx
521307101305xxxx|xx|xxxx|xxx
521307101485xxxx|xx|xxxx|xxx
Text File 2:
521307
407166
If the lines in the 2nd text file exist in the 1st text file, I want it to show me all the matching lines from the 1st file
OUTPUT:
521307101485xxxx
521307101305xxxx
407166210197xxxx
This can be a tricky problem to solve. If you are dealing with large files, or don't know how large your files will be, you have to find a way to solve the problem without reading either file into memory all at once.
In order to do that, you need to create some sort of efficient structure for the data in file 1 that you can search for each ID in file 2, that also allows you to retrieve the full records for file 1 after you have determined the matches. This is exactly what trees are made for.
Here is a solution that reads in the data in file 1, creates a tree structure from the first column of each row, and keeps track of the byte offsets from the file where the strings appear. This allows you to search using any length of ID prefix (searching with "4" would return the first two lines, "40" only the second).
There are two classes, CharNode represents a single node in the tree, and IdTree, which manages the structure of nodes, handles ingesting the files, and searching.
<?php
class CharNode implements JsonSerializable
{
private string $char;
private array $byteOffsets = [];
private array $children = [];
/**
* CharNode constructor.
* #param string $char
* #param bool $terminal
*/
public function __construct(string $char)
{
$this->char = $char;
}
/**
* #return string
*/
public function getChar(): string
{
return $this->char;
}
/**
* #param string $char
*/
public function setChar(string $char): void
{
$this->char = $char;
}
/**
* #return array
*/
public function getChildren(): array
{
return $this->children;
}
/**
* #param array $children
*/
public function setChildren(array $children): void
{
$this->children = $children;
}
/**
* #return array
*/
public function getByteOffsets(): array
{
return $this->byteOffsets;
}
/**
* #param array $byteOffsets
*/
public function setByteOffsets(array $byteOffsets): void
{
$this->byteOffsets = $byteOffsets;
}
/**
* #param int $byteOffset
* #return void
*/
public function addByteOffset(int $byteOffset): void
{
$this->byteOffsets[] = $byteOffset;
}
/**
* #param array $charVector
* #param int $byteOffset
* #return void
*/
public function ingestCharVector(array $charVector, int $byteOffset)
{
$char = array_shift($charVector);
if (!array_key_exists($char, $this->children))
{
$newNode = new CharNode($char);
$this->children[$char] = $newNode;
}
$currChild = $this->children[$char];
$currChild->addByteOffset($byteOffset);
if (!empty($charVector))
{
$currChild->ingestCharVector($charVector, $byteOffset);
}
}
public function jsonSerialize()
{
return [
'char' => $this->char,
'byteOffsets' => $this->byteOffsets,
'children' => array_values($this->children)
];
}
}
class IdTree implements JsonSerializable
{
private array $tree = [];
private array $byteOffsetOutput = [];
private string $filePath;
/**
* #param string $filePath
* #param string $delimiter
* #throws Exception
*/
public function __construct(string $filePath, string $delimiter = '|')
{
$this->filePath = $filePath;
$fh = fopen($filePath, 'r');
if (!$fh)
{
throw new Exception('Could not open file ' . $filePath);
}
$currByteOffset = 0;
while (($currRow = fgetcsv($fh, null, $delimiter)))
{
$this->ingestWord($currRow[0], $currByteOffset);
$currByteOffset = ftell($fh);
}
fclose($fh);
}
/**
* #param string $idFilePath
* #return array
* #throws Exception
*/
public function getByteOffsetsForIdFile(string $idFilePath): array
{
$byteOffsets = [];
$fh = fopen($idFilePath, 'r');
if (!$fh)
{
throw new Exception('Could not open file ' . $idFilePath);
}
while (($currLine = fgets($fh)))
{
$currByteOffsets = $this->findByteOffsetsForId(trim($currLine));
$byteOffsets = array_merge($byteOffsets, $currByteOffsets);
}
fclose($fh);
asort($byteOffsets);
return $byteOffsets;
}
/**
* #param string $idFilePath
* #param bool $firstColumnOnly
* #return array
* #throws Exception
*/
public function getLinesMatchingIdFile(string $idFilePath, bool $firstColumnOnly = false): array
{
$byteOffsets = $this->getByteOffsetsForIdFile($idFilePath);
$fh = fopen($this->filePath, 'r');
$output = [];
foreach ($byteOffsets as $currOffset)
{
fseek($fh, $currOffset);
$currRow = fgetcsv($fh, null, '|');
$output[] = ($firstColumnOnly) ? $currRow[0] : $currRow;
}
return $output;
}
public function ingestWord(string $word, int $byteOffset): void
{
$word = $this->formatWord($word);
if (empty($word))
{
return;
}
$charVector = str_split($word, 1);
$this->ingestCharVector($charVector, $byteOffset);
}
/**
* #param array $charVector
* #param int $byteOffset
* #return void
*/
public function ingestCharVector(array $charVector, int $byteOffset): void
{
$char = array_shift($charVector);
if (!array_key_exists($char, $this->tree))
{
$this->tree[$char] = new CharNode($char);
}
$currChild = $this->tree[$char];
if (!empty($charVector))
{
$currChild->ingestCharVector($charVector, $byteOffset);
}
}
/**
* #param string $term
* #return array
*/
public function findByteOffsetsForId(string $term): array
{
// Reset state
$this->byteOffsetOutput = [];
$this->stringBuffer = [];
$word = $this->formatWord($term);
if (empty($word))
{
return [];
}
$charVector = str_split($word, 1);
$this->branchSearch($charVector, $this->tree);
return $this->byteOffsetOutput;
}
/**
* #param array $charVector
* #param array $charNodeSet
* #return void
*/
private function branchSearch(array $charVector, array $charNodeSet): void
{
if (empty($charNodeSet))
{
return;
}
if (!empty($charVector))
{
$currChar = array_shift($charVector);
if (!array_key_exists($currChar, $charNodeSet))
{
return;
}
/**
* #var $currCharNode CharNode
*/
$currCharNode = $charNodeSet[$currChar];
// If this is the end of the search term, set th eline numbers
if (empty($charVector))
{
$this->byteOffsetOutput = array_merge($this->byteOffsetOutput, $currCharNode->getByteOffsets());
}
$this->branchSearch($charVector, $currCharNode->getChildren());
}
}
/**
* #param string $word
* #return array|string|string[]|null
*/
private function formatWord(string $word)
{
$word = strtolower($word);
$word = preg_replace("/[^a-z0-9 ]/", '', $word);
return $word;
}
public function jsonSerialize()
{
return array_values($this->tree);
}
}
That looks like a lot of code, but most of it is fairly idiomatic tree logic.
Using it is dead simple:
// Instantiate and load our tree
$tree = new IdTree('file1.txt');
// Get all matching rows
$matchingRows = $tree->getLinesMatchingIdFile('file2.txt');
print_r($matchingRows);
Output:
Array
(
[0] => Array
(
[0] => 407166210197xxxx
[1] => xx
[2] => xxxx
[3] => xxx
)
[1] => Array
(
[0] => 521307101305xxxx
[1] => xx
[2] => xxxx
[3] => xxx
)
[2] => Array
(
[0] => 521307101485xxxx
[1] => xx
[2] => xxxx
[3] => xxx
)
)
It was not clear to me whether you wanted the entire row for each match, or just the first column, so I added a flag that allows that.
// Get only the first column of each line
$matchingIds = $tree->getLinesMatchingIdFile('file2.txt', true);
print_r($matchingIds);
Output:
Array
(
[0] => 407166210197xxxx
[1] => 521307101305xxxx
[2] => 521307101485xxxx
)
There is some extra stuff you may not need, like the JSON output, which is useful for visualizing how the structure works. You could also make this more efficient if you know your data will always be formatted in certain ways (if your search IDs will always be within certain lengths, etc). You could still run into memory problems if you are processing truly massive data files. This is just a basic example of how you can go about solving problems like this "for real".
You can try to use preg_match function to find all strings
$file1 = file_get_contents('text1.txt');
$file2 = file_get_contents('text2.txt');
$file2 = explode("\r\n", $file2);
foreach($file2 as $item){
preg_match_all('#'.$item.'.+#', $file1, $matches);
$result[] = $matches;
}
Result:
Array
(
[0] => Array
(
[0] => Array
(
[0] => 521307101305xxxx|xx|xxxx|xxx
[1] => 521307101485xxxx|xx|xxxx|xxx
)
)
[1] => Array
(
[0] => Array
(
[0] => 407166210197xxxx|xx|xxxx|xxx
)
)
)
but I think, what use preg_match to find a string, it's not best solution
I have imported a config library into my project to allow me to manage data files with PHP with ease.
/**
* Config Class for simple config manipulation of multiple formats.
*/
class Config{
const DETECT = -1; //Detect by file extension
const PROPERTIES = 0; // .properties
const CNF = Config::PROPERTIES; // .cnf
const JSON = 1; // .js, .json
const YAML = 2; // .yml, .yaml
//const EXPORT = 3; // .export, .xport
const SERIALIZED = 4; // .sl
const ENUM = 5; // .txt, .list, .enum
const ENUMERATION = Config::ENUM;
/** #var array */
private $config = [];
private $nestedCache = [];
/** #var string */
private $file;
/** #var bool */
private $correct = false;
/** #var int */
private $type = Config::DETECT;
/** #var int */
private $jsonOptions = JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING;
public static $formats = [
"properties" => Config::PROPERTIES,
"cnf" => Config::CNF,
"conf" => Config::CNF,
"config" => Config::CNF,
"json" => Config::JSON,
"js" => Config::JSON,
"yml" => Config::YAML,
"yaml" => Config::YAML,
//"export" => Config::EXPORT,
//"xport" => Config::EXPORT,
"sl" => Config::SERIALIZED,
"serialize" => Config::SERIALIZED,
"txt" => Config::ENUM,
"list" => Config::ENUM,
"enum" => Config::ENUM,
];
/**
* #param string $file Path of the file to be loaded
* #param int $type Config type to load, -1 by default (detect)
* #param array $default Array with the default values that will be written to the file if it did not exist
* #param null &$correct Sets correct to true if everything has been loaded correctly
*/
public function __construct($file, $type = Config::DETECT, $default = [], &$correct = null){
$this->load($file, $type, $default);
$correct = $this->correct;
}
/**
* #param $k
* #param $v
*/
public function __set($k, $v){
$this->set($k, $v);
}
public function set($k, $v = true){
$this->config[$k] = $v;
foreach($this->nestedCache as $nestedKey => $nvalue){
if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){
unset($this->nestedCache[$nestedKey]);
}
}
}
/**
* #param $k
* #param mixed $default
*
* #return bool|mixed
*/
public function get($k, $default = false){
return ($this->correct and isset($this->config[$k])) ? $this->config[$k] : $default;
}
/**
* #param $file
* #param int $type
* #param array $default
*
* #return bool
*/
public function load($file, $type = Config::DETECT, $default = []){
$this->correct = true;
$this->type = (int) $type;
$this->file = $file;
if(!is_array($default)){
$default = [];
}
if(!file_exists($file)){
$this->config = $default;
$this->save();
}else{
if($this->type === Config::DETECT){
$extension = explode(".", basename($this->file));
$extension = strtolower(trim(array_pop($extension)));
if(isset(Config::$formats[$extension])){
$this->type = Config::$formats[$extension];
}else{
$this->correct = false;
}
}
if($this->correct === true){
$content = file_get_contents($this->file);
switch($this->type){
case Config::PROPERTIES:
case Config::CNF:
$this->parseProperties($content);
break;
case Config::JSON:
$this->config = json_decode($content, true);
break;
case Config::YAML:
$content = self::fixYAMLIndexes($content);
$this->config = yaml_parse($content);
break;
case Config::SERIALIZED:
$this->config = unserialize($content);
break;
case Config::ENUM:
$this->parseList($content);
break;
default:
$this->correct = false;
return false;
}
if(!is_array($this->config)){
$this->config = $default;
}
if($this->fillDefaults($default, $this->config) > 0){
$this->save();
}
}else{
return false;
}
}
return true;
}
In my script, with gc_enable() enabled, I created some data files using this class.
$data = new Config($this->path . "file.yml", Config::YAML);
$data->set("key", "this will leak memory");
I never saved any references to $data, yet my system is leaking memory. I do not know why PHP is leaking memory to an object that has no references. What could be causing this memory leak?
I am trying to write a class that will allow me to write data to a file and then read it. In some cases, I also lock the file for write, then unlock it once the writing is done.
The problem that I am having is when trying to get the content of the file using my getCache() method the file becomes empty. It seems that when the getCache() method is called the content of the file are deleted for some reason.
Here is my class
<?php
namespace ICWS;
use \ICWS\showVar;
use \DirectoryIterator;
/**
* CacheHandler
*
* #package ICWS
*/
class CacheHandler {
/* Max time second allowed to keep a session File */
private $maxTimeAllowed = 300;
private $filename = '';
private $handle;
public function __construct( $filename ){
$this->filename = $filename;
// 5% chance to collect garbage when the class is initialized.
if(rand(1, 100) <= 5){
$this->collectGarbage();
}
}
/**
* Add/Update the cached array in the session
*
* #param string $key
* #param bigint $id
* #param array $field
* #return void
*/
public function updateCache($key, $id, array $field)
{
$currentVal = (object) $field;
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
//create a new if the $id does not exists in the cache
if( isset($storage[$key]) && array_key_exists($id, $storage[$key]) ){
$currentVal = (object) $storage[$key][$id];
foreach($field as $k => $v){
$currentVal->$k = $v; //update existing key or add a new one
}
}
$storage[$key][$id] = $currentVal;
new showVar($storage);
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
}
/**
* gets the current cache/session for a giving $key
*
* #param string $key
* #return object or boolean
*/
public function getCache($key)
{
$value = false;
$this->openFile();
$storage = $this->readFile();
if(isset($storage[$key])){
$value = $storage[$key];
}
$this->closeFile();
return $value;
}
/**
* removes the $id from the cache/session
*
* #param string $key
* #param bigint $id
* #return boolean
*/
public function removeCache($key, $id)
{
$this->openFile();
$this->lockFile();
$storage = $this->readFile();
if( !isset($storage[$key][$id])){
$this->unlockFile();
$this->closeFile();
return false;
}
unset($storage[$key][$id]);
$this->updateFile($storage);
$this->unlockFile();
$this->closeFile();
return true;
}
/**
* unset a session
*
* #param argument $keys
* #return void
*/
public function truncateCache()
{
if(file_exists($this->filename)){
unlink($this->filename);
}
}
/**
* Open a file in a giving mode
*
* #params string $mode
* #return void
*/
private function openFile( $mode = "w+"){
$this->handle = fopen($this->filename, $mode);
if(!$this->handle){
throw new exception('The File could not be opened!');
}
}
/**
* Close the file
* #return void
*/
private function closeFile(){
fclose($this->handle);
$this->handle = null;
}
/**
* Update the file with array data
* #param array $data the array in that has the data
* #return void
*/
private function updateFile(array $data = array() ){
$raw = serialize($data);
fwrite($this->handle, $raw);
}
/**
* Read the data from the opned file
*
* #params string $mode
* #return array of the data found in the file
*/
private function readFile() {
$length = filesize($this->filename);
if($length > 0){
rewind($this->handle);
$raw = fread($this->handle, filesize($this->filename));
return unserialize($raw);
}
return array();
}
/**
* Lock the file
*
* #return void
*/
private function lockFile( $maxAttempts = 100, $lockType = LOCK_EX) {
$i = 0;
while($i <= $maxAttempts){
// acquire an exclusive lock
if( flock($this->handle, LOCK_EX) ){
break;
}
++$i;
}
}
/**
* Unlock the file
*
* #return void
*/
private function unlockFile() {
fflush($this->handle);
flock($this->handle, LOCK_UN);
}
/**
* Remove add cache files
*
* #return void
*/
private function collectGarbage(){
$mydir = dirname($this->filename);
$dir = new DirectoryIterator( $mydir );
$time = strtotime("now");
foreach ($dir as $file) {
if ( !$file->isDot()
&& $file->isFile()
&& ($time - $file->getATime()) > $this->maxTimeAllowed
&& isset($file->getFilename) && $file->getFilename != 'index.html'
) {
unlink($file->getPathName());
}
}
}
}
This is how I call my class
<?php
require 'autoloader.php';
try {
$cache = new \ICWS\CacheHandler('cache/12300000');
$field = array('name' => 'Mike A', 'Address' => '123 S Main', 'phone' => '2152456245', 'ext' => 123);
$cache->updateCache('NewKey', '123456', $field);
echo '<pre>';
print_r($cache->getCache('NewKey'));
echo '</pre>';
} catch (exception $e){
echo $e->getMessage();
}
?>
When commenting the line print_r($cache->getCache('NewKey')); The file 12300000 will have the data below as expected
a:1:{s:6:"NewKey";a:1:{i:123456;O:8:"stdClass":4:{s:4:"name";s:6:"Mike A";s:7:"Address";s:10:"123 S Main";s:5:"phone";s:10:"2152456245";s:3:"ext";i:123;}}}
However, when the method print_r($cache->getCache('NewKey')); is called the file becomes empty.
What am I doing wrong here? Why is my print_r($cache->getCache('NewKey')); method is emptying out the file?
openFile($mode = "w+")
The documentation for fopen() says:
'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it.
So it resets the file, deleting everything in it.
Probably you want mode a+:
'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does not exist, attempt to create it. In this mode, fseek()() only affects the reading position, writes are always appended.
I have an issue in accessing the array in php.
$path = "['a']['b']['c']";
$value = $array.$path;
In the above piece of code I have an multidimensional array named $array.
$path is a dynamic value which I would get from database.
Now I want to retrieve the value from $array using $path but I am not able to.
$value = $array.$path
returns me
Array['a']['b']['c']
rather than the value.
I hope I have explained my question properly.
You have two options. First (evil) if to use eval() function - i.e. interpret your string as code.
Second is to parse your path. That will be:
//$path = "['a']['b']['c']";
preg_match_all("/\['(.*?)'\]/", $path, $rgMatches);
$rgResult = $array;
foreach($rgMatches[1] as $sPath)
{
$rgResult=$rgResult[$sPath];
}
The Kohana framework "Arr" class (API) has a method (Arr::path) that does something similar to what you are requesting. It simply takes an array and a path (with a . as delimiter) and returns the value if found. You could modify this method to suit your needs.
public static function path($array, $path, $default = NULL, $delimiter = NULL)
{
if ( ! Arr::is_array($array))
{
// This is not an array!
return $default;
}
if (is_array($path))
{
// The path has already been separated into keys
$keys = $path;
}
else
{
if (array_key_exists($path, $array))
{
// No need to do extra processing
return $array[$path];
}
if ($delimiter === NULL)
{
// Use the default delimiter
$delimiter = Arr::$delimiter;
}
// Remove starting delimiters and spaces
$path = ltrim($path, "{$delimiter} ");
// Remove ending delimiters, spaces, and wildcards
$path = rtrim($path, "{$delimiter} *");
// Split the keys by delimiter
$keys = explode($delimiter, $path);
}
do
{
$key = array_shift($keys);
if (ctype_digit($key))
{
// Make the key an integer
$key = (int) $key;
}
if (isset($array[$key]))
{
if ($keys)
{
if (Arr::is_array($array[$key]))
{
// Dig down into the next part of the path
$array = $array[$key];
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Found the path requested
return $array[$key];
}
}
elseif ($key === '*')
{
// Handle wildcards
$values = array();
foreach ($array as $arr)
{
if ($value = Arr::path($arr, implode('.', $keys)))
{
$values[] = $value;
}
}
if ($values)
{
// Found the values requested
return $values;
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Unable to dig deeper
break;
}
}
while ($keys);
// Unable to find the value requested
return $default;
}
I was hoping to find an elegant solution to nested array access without throwing undefined index errors, and this post hits high on google. I'm late to the party, but I wanted to weigh in for future visitors.
A simple isset($array['a']['b']['c'] can safely check nested values, but you need to know the elements to access ahead of time. I like the dot notation for accessing multidimensional arrays, so I wrote a class of my own. It does require PHP 5.6.
This class parses a string path written in dot-notation and safely accesses the nested values of the array or array-like object (implements ArrayAccess). It will return the value or NULL if not set.
use ArrayAccess;
class SafeArrayGetter implements \JsonSerializable {
/**
* #var array
*/
private $data;
/**
* SafeArrayGetter constructor.
*
* #param array $data
*/
public function __construct( array $data )
{
$this->data = $data;
}
/**
* #param array $target
* #param array ...$indices
*
* #return array|mixed|null
*/
protected function safeGet( array $target, ...$indices )
{
$movingTarget = $target;
foreach ( $indices as $index )
{
$isArray = is_array( $movingTarget ) || $movingTarget instanceof ArrayAccess;
if ( ! $isArray || ! isset( $movingTarget[ $index ] ) ) return NULL;
$movingTarget = $movingTarget[ $index ];
}
return $movingTarget;
}
/**
* #param array ...$keys
*
* #return array|mixed|null
*/
public function getKeys( ...$keys )
{
return static::safeGet( $this->data, ...$keys );
}
/**
* <p>Access nested array index values by providing a dot notation access string.</p>
* <p>Example: $safeArrayGetter->get('customer.paymentInfo.ccToken') ==
* $array['customer']['paymentInfo']['ccToken']</p>
*
* #param $accessString
*
* #return array|mixed|null
*/
public function get( $accessString )
{
$keys = $this->parseDotNotation( $accessString );
return $this->getKeys( ...$keys );
}
/**
* #param $string
*
* #return array
*/
protected function parseDotNotation( $string )
{
return explode( '.', strval( $string ) );
}
/**
* #return array
*/
public function toArray()
{
return $this->data;
}
/**
* #param int $options
* #param int $depth
*
* #return string
*/
public function toJson( $options = 0, $depth = 512 )
{
return json_encode( $this, $options, $depth );
}
/**
* #param array $data
*
* #return static
*/
public static function newFromArray( array $data )
{
return new static( $data );
}
/**
* #param \stdClass $data
*
* #return static
*/
public static function newFromObject( \stdClass $data )
{
return new static( json_decode( json_encode( $data ), TRUE ) );
}
/**
* Specify data which should be serialized to JSON
* #link http://php.net/manual/en/jsonserializable.jsonserialize.php
* #return array data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* #since 5.4.0
*/
function jsonSerialize()
{
return $this->toArray();
}
}
i have a map stored as a multidimensional array ($map[row][col]) and i'd wish to create a path from point A to point B.
since i can have some obstacles with turns, corners etc etc, i'd wish to use the A* search to calculate the fastest path.
so the general function is
f(x) = g(x) + h(x)
and i have all of these values. g(x) is cost of the move (and it's saved on the map); h(x) is the linear distance between A and B.
so i have everything i need, but i have a question: how can i organize everything?
i have no need to test for alternative paths, since a square on the map can be passable or not, so when i reach the target it should be the shortest one.
how can i organize everything?
i tried with multidimensional array, but i get lost.. :(
EDIT
i worked out some code, it's pretty a wall of text :)
//$start = array(28, 19), $end = array(14, 19)
//$this->map->map is a multidimensional array, everything has a cost of 1, except for
//blocking squares that cost 99
//$this->map->map == $this->radar
//blocking square at 23-17, 22-18, 22-19, 22-20, 23-21, 19-17, 20-18,20-19,20-20,19-21
//they are like 2 specular mustache :P
function createPath($start, $end)
{
$found = false;
$temp = $this->cost($start, $end);
foreach($temp as $t){
if($t['cost'] == $this->map->map[$end[0]][$end[1]]) $found = true;
$this->costStack[$t['cost']][] = array('grid' => $t['grid'], 'dir' => $t['dir']);
}
ksort($this->costStack);
if(!$found) {
foreach($this->costStack as $k => $stack){
foreach($stack as $kn => $node){
$curNode = $node['grid'];
unset($this->costStack[$k][$kn]);
break;
}
if(!count($this->costStack[$k])) unset($this->costStack[$k]);
break;
}
$this->createPath($curNode, $end);
}
}
function cost($current, $target)
{
$return = array();
//$AIM = array('n' => array(-1, 0),'e' => array( 0, 1),'s' => array( 1, 0),'w' => array( 0, -1));
foreach($this->AIM as $direction => $offset){
$position[0] = $current[0] + $offset[0];
$position[1] = $current[1] + $offset[1];
//radar is a copy of the map
if ( $this->radar[$position[0]][$position[1]] == 'V') continue;
else $this->radar[$position[0]][$position[1]] = 'V';
$h = (int) $this->distance($position, $target);
$g = $this->map->map[$position[0]][$position[1]];
$return[] = array('grid' => $position,
'dir' => $direction,
'cost' => $h + $g);
}
return $return;
}
i hope you can understand everything, i tried to be clear as much as possible.
finally i can get to my destination, expanding only cheaper nodes, but now i have a problem.
how can i turn it into directions? i have to store a stack of orders (ie n, n, e etc etc), how can i identify a path inside these values?
My structure was:
Have a Grid-class for holding all possible nodes (propably your array
goes here)
Have a Node-class representing the nodes. Nodes will also calculated costs and store predecessor/g-values set by AStar
Have a AStar class, which will only get two nodes (e.g. startNode, endNode)
Have a PriorityQueue as your open-list
when a Node is asked (by AStar) about it's neighbors, delegated that call to Grid
I'll try to collect some code samples from a prior project, could take a while though.
Update
(found my old project ;))
It's probably not exactly what you're looking for, but maybe it's a start.
So using the files below, and mazes defined like:
00000000000000000000000
00000000000000000000000
0000000000W000000000000
0000000000W000000000000
0000000000W000000000000
0000000000W00000WWWWWWW
0000000000W000000000000
S000000000W00000000000E
(test/maze.txt)
You'll get something like this:
00000000000000000000000
0000000000X000000000000
000000000XWXX0000000000
00000000X0W00X000000000
000000XX00W000X00000000
00000X0000W0000XWWWWWWW
0000X00000W00000XXX0000
SXXX000000W00000000XXXE
index.php
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');
header('Content-Type: text/plain; charset="utf-8"');
// simple autoloader
function __autoload($className) {
$path = '/lib/' . str_replace('_', '/', $className) . '.php';
foreach (explode(PATH_SEPARATOR, get_include_path()) as $prefix) {
if (file_exists($prefix . $path)) {
require_once $prefix . $path;
}
}
}
// init maze
$maze = new Maze_Reader('test/maze.txt');
$startNode = $maze->getByValue('S', true);
$endNode = $maze->getByValue('E', true);
$astar = new AStar;
if ($astar->getPath($startNode, $endNode)) {
do {
if (!in_array($endNode->value(), array('S', 'E'))) {
$endNode->value('X');
}
} while ($endNode = $endNode->predecessor());
}
echo $maze;
/lib/AStar.php
/**
* A simple AStar implementation
*/
class AStar
{
protected $openList;
protected $closedList;
/**
* Constructs the astar object
*/
public function __construct() {
$this->openList = new PriorityQueue;
$this->closedList = new SplObjectStorage;
}
public function getPath($startNode, $endNode) {
$this->openList->insert(0, $startNode);
while (!$this->openList->isEmpty()) {
$currentNode = $this->openList->extract();
if ($currentNode->equals($endNode)) {
return $currentNode;
}
$this->expandNode($currentNode, $endNode);
$this->closedList[$currentNode] = true;
}
return false;
}
protected function expandNode($currentNode, $endNode) {
foreach ($currentNode->successors() as $successor) {
if (isset($this->closedList[$successor])) {
continue;
}
$tentative_g = $currentNode->g() + $currentNode->distance($successor);
if ($this->openList->indexOf($successor) > -1 && $tentative_g >= $successor->g()) {
continue;
}
$successor->predecessor($currentNode);
$successor->g($tentative_g);
$f = $tentative_g + $successor->distance($endNode);
if ($this->openList->indexOf($successor) > -1) {
$this->openList->changeKey($successor, $f);
continue;
}
$this->openList->insert($f, $successor);
}
}
}
/lib/PriorityQueue.php
class PriorityQueue
{
protected $keys = array();
protected $values = array();
/**
* Helper function to swap two <key>/<value> pairs
*
* #param Integer a
* #param Integer b
* #return Integer b
*/
protected function swap($a, $b) {
// swap keys
$c = $this->keys[$a];
$this->keys[$a] = $this->keys[$b];
$this->keys[$b] = $c;
// swap values
$c = $this->values[$a];
$this->values[$a] = $this->values[$b];
$this->values[$b] = $c;
return $b;
}
/**
* Heapify up
*
* #param Integer pos
* #return void
*/
protected function upHeap($pos) {
while ($pos > 0) {
$parent = ($pos - 1) >> 2;
if ($this->compare($this->keys[$pos], $this->keys[$parent]) >= 0) {
break;
}
$pos = $this->swap($pos, $parent);
}
}
/**
* Heapify down
*
* #param Integer pos
* #return void
*/
protected function downHeap($pos) {
$len = sizeof($this->keys);
$max = ($len - 1) / 2;
while ($pos < $max) {
$child = 2 * $pos + 1;
if ($child < $len - 1 && $this->compare($this->keys[$child], $this->keys[$child + 1]) > 0) {
$child += 1;
}
if ($this->compare($this->keys[$pos], $this->keys[$child]) <= 0) {
break;
}
$pos = $this->swap($pos, $child);
}
}
/**
* Insert an <key>/<value> pair into the queue
*
* #param Object key
* #param Object value
* #return this
*/
public function insert($key, $value) {
$this->keys[] = $key;
$this->values[] = $value;
$this->upHeap(sizeof($this->keys) - 1);
return $this;
}
/**
* Extract the top <value>
*
* #return Object
*/
public function extract() {
$resultValue = $this->values[0];
$lastValue = array_pop($this->values);
$lastKey = array_pop($this->keys);
if (sizeof($this->keys) > 0) {
$this->values[0] = $lastValue;
$this->keys[0] = $lastKey;
$this->downHeap(0);
}
return $resultValue;
}
/**
* Changes the <key> of a <value>
*
* #param Object key
* #param Object value
* #return this
*/
public function changeKey($key, $value) {
$pos = $this->indexOf($value);
if ($pos !== false) {
$this->keys[$pos] = $key;
$this->upHeap($pos);
}
return $this;
}
/**
* Returns the index of <value> or false if <value> is not in the queue
*
* #return false|Int
*/
public function indexOf($value) {
return array_search($value, $this->values, true);
}
/**
* Used to campare two <key>s.
*
* #param Object a
* #param Object b
* #return Number
*/
protected function compare($a, $b) {
return $a - $b;
}
/**
* Returns true if the queue is empty
*
* #return Boolean
*/
public function isEmpty() {
return sizeof($this->keys) === 0;
}
}
/lib/Maze/Reader.php
class Maze_Reader implements IteratorAggregate
{
/**
* The initial maze
* #var string
*/
protected $rawMaze;
/**
* A tow dimensional array holding the parsed maze
* #var array
*/
protected $map = array();
/**
* A flat array holding all maze nodes
* #var array
*/
protected $nodes = array();
/**
* A value map for easier access
* #var array
*/
protected $valueMap = array();
/**
* Constructs a maze reader
*
* #param string $file A path to a maze file
*/
public function __construct($file) {
$this->rawMaze = file_get_contents($file);
$this->parseMaze($this->rawMaze);
}
/**
* Parses the raw maze into usable Maze_Nodes
*
* #param string $maze
*/
protected function parseMaze($maze) {
foreach (explode("\n", $maze) as $y => $row) {
foreach (str_split(trim($row)) as $x => $cellValue) {
if (!isset($this->map[$x])) {
$this->map[$x] = array();
}
if (!isset($this->valueMap[$cellValue])) {
$this->valueMap[$cellValue] = array();
}
$this->nodes[] = new Maze_Node($x, $y, $cellValue, $this);;
$this->map[$x][$y] =& $this->nodes[sizeof($this->nodes) - 1];
$this->valueMap[$cellValue][] =& $this->nodes[sizeof($this->nodes) - 1];
}
}
}
/**
* Returns the neighobrs of $node
*
* #return array
*/
public function getNeighbors(Maze_Node $node) {
$result = array();
$top = $node->y() - 1;
$right = $node->x() + 1;
$bottom = $node->y() + 1;
$left = $node->x() - 1;
// top left
if (isset($this->map[$left], $this->map[$left][$top])) {
$result[] = $this->map[$left][$top];
}
// top center
if (isset($this->map[$node->x()], $this->map[$node->x()][$top])) {
$result[] = $this->map[$node->x()][$top];
}
// top right
if (isset($this->map[$right], $this->map[$right][$top])) {
$result[] = $this->map[$right][$top];
}
// right
if (isset($this->map[$right], $this->map[$right][$node->y()])) {
$result[] = $this->map[$right][$node->y()];
}
// bottom right
if (isset($this->map[$right], $this->map[$right][$bottom])) {
$result[] = $this->map[$right][$bottom];
}
// bottom center
if (isset($this->map[$node->x()], $this->map[$node->x()][$bottom])) {
$result[] = $this->map[$node->x()][$bottom];
}
// bottom left
if (isset($this->map[$left], $this->map[$left][$bottom])) {
$result[] = $this->map[$left][$bottom];
}
// left
if (isset($this->map[$left], $this->map[$left][$node->y()])) {
$result[] = $this->map[$left][$node->y()];
}
return $result;
}
/**
* #IteratorAggregate
*/
public function getIterator() {
return new ArrayIterator($this->nodes);
}
/**
* Returns a node by value
*
* #param mixed $value
* #param boolean $returnOne
* #param mixed $fallback
* #return mixed
*/
public function getByValue($value, $returnOne = false, $fallback = array()) {
$result = isset($this->valueMap[$value]) ? $this->valueMap[$value] : $fallback;
if ($returnOne && is_array($result)) {
$result = array_shift($result);
}
return $result;
}
/**
* Simple output
*/
public function __toString() {
$result = array();
foreach ($this->map as $x => $col) {
foreach ($col as $y => $node) {
$result[$y][$x] = (string)$node;
}
}
return implode("\n", array_map('implode', $result));
}
}
/lib/Maze/Node.php
class Maze_Node
{
protected $x;
protected $y;
protected $value;
protected $maze;
protected $g;
protected $predecessor;
/**
* #param Integer $x
* #param Integer $y
* #param mixed $value
* #param Maze_Reader $maze
*/
public function __construct($x, $y, $value, $maze) {
$this->x = $x;
$this->y = $y;
$this->value = $value;
$this->maze = $maze;
}
/**
* Getter for x
*
* #return Integer
*/
public function x() {
return $this->x;
}
/**
* Getter for y
*
* #return Integer
*/
public function y() {
return $this->y;
}
/**
* Setter/Getter for g
*
* #param mixed $g
* #return mixed
*/
public function g($g = null) {
if ($g !== null) {
$this->g = $g;
}
return $this->g;
}
/**
* Setter/Getter for value
*
* #param mixed $value
* #return mixed
*/
public function value($value = null) {
if ($value !== null) {
$this->value = $value;
}
return $this->value;
}
/**
* Setter/Getter for predecessor
*
* #param Maze_Node $predecessor
* #return Maze_Node|null
*/
public function predecessor(Maze_Node $predecessor = null) {
if ($predecessor !== null) {
$this->predecessor = $predecessor;
}
return $this->predecessor;
}
/**
* simple distance getter
*
* #param Maze_Node $that
* #return Float
*/
public function distance(Maze_Node $that) {
if ($that->value() === 'W') {
return PHP_INT_MAX;
}
return sqrt(pow($that->x() - $this->x, 2) + pow($that->y() - $this->y, 2));
}
/**
* Test for equality
*
* #param Maze_Node $that
* #return boolean
*/
public function equals(Maze_Node $that) {
return $this == $that;
}
/**
* Returns the successors of this node
*
* #return array
*/
public function successors() {
return $this->maze->getNeighbors($this);
}
/**
* For debugging
*
* #return string
*/
public function __toString() {
return (string)$this->value;
}
}