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?
Related
I'm using Phalcon Redis backend to store some data. I later try to access this data in Lua language embedded into nginx. What drives me crazy is that Phalcon adds some garbage prefixes to Redis keys and some terrible prefixes to values. So, if I store this pair in Redis - (abc, querty) - this is what is really stored:
(_PHCRabc, s:6:"querty")
Is it possible to disable all this garbage and continue working with Phalcon Redis backend?
According to the the source it is not possible to disable it with option: https://github.com/phalcon/cphalcon/blob/master/phalcon/cache/backend/redis.zep
public function get(string keyName, int lifetime = null) -> var | null
let lastKey = "_PHCR" . prefix . keyName;
public function save(keyName = null, content = null, lifetime = null, boolean stopBuffer = true) -> boolean
lastKey = "_PHCR" . prefixedKey,
Also quoting the docs:
This adapter uses the special redis key “_PHCR” to store all the keys
internally used by the adapter
I read somewhere that this is done in order to be able to flush Phalcon generated cache files.
Your best option would be to extend the \Phalcon\Cache\Backend\Redis class and overwrite the save/get methods. And after use your class in the service:
// Cache
$di->setShared('cache', function() use ($config) {
return new MyCustomRedis(
new \Phalcon\Cache\Frontend\Json(['lifetime' => 172800]), // 2d
$config->redis
);
});
You can override the redis adapter like this.
<?php
namespace App\Library\Cache\Backend;
use Phalcon\Cache\Exception;
class Redis extends \Phalcon\Cache\Backend\Redis
{
/**
* #var \Redis
*/
protected $_redis;
/**
* {#inheritdoc}
*
* #param string $keyName
* #param integer $lifetime
* #return mixed|null
*/
public function get($keyName, $lifetime = null)
{
$redis = $this->getRedis();
/**
* #var \Phalcon\Cache\FrontendInterface $frontend
*/
$frontend = $this->_frontend;
$lastKey = $this->getKeyName($keyName);
$this->_lastKey = $lastKey;
$content = $redis->get($lastKey);
if ($content === false) {
return null;
}
if (is_numeric($content)) {
return $content;
}
return $frontend->afterRetrieve($content);
}
/**
* {#inheritdoc}
*
* #param string $keyName
* #param string $content
* #param int $lifetime
* #param bool $stopBuffer
* #return bool
*
* #throws Exception
*/
public function save($keyName = null, $content = null, $lifetime = null, $stopBuffer = true)
{
if ($keyName === null) {
$lastKey = $this->_lastKey;
} else {
$lastKey = $this->getKeyName($keyName);
$this->_lastKey = $lastKey;
}
if (!$lastKey) {
throw new Exception('The cache must be started first');
}
$redis = $this->getRedis();
/**
* #var \Phalcon\Cache\FrontendInterface $frontend
*/
$frontend = $this->_frontend;
if ($content === null) {
$cachedContent = $frontend->getContent();
} else {
$cachedContent = $content;
}
/**
* Prepare the content in the frontend
*/
if (!is_numeric($cachedContent)) {
$preparedContent = $frontend->beforeStore($cachedContent);
} else {
$preparedContent = $cachedContent;
}
if ($lifetime === null) {
$tmp = $this->_lastLifetime;
$ttl = $tmp ? $tmp : $frontend->getLifetime();
} else {
$ttl = $lifetime;
}
$success = $redis->set($lastKey, $preparedContent);
if (!$success) {
throw new Exception('Failed storing the data in redis');
}
if ($ttl > 0) {
$redis->setTimeout($lastKey, $ttl);
}
$isBuffering = $frontend->isBuffering();
if ($stopBuffer === true) {
$frontend->stop();
}
if ($isBuffering === true) {
echo $cachedContent;
}
$this->_started = false;
return $success;
}
/**
* {#inheritdoc}
*
* #param string $keyName
* #return bool
*/
public function delete($keyName)
{
$redis = $this->getRedis();
$lastKey = $this->getKeyName($keyName);
return (bool)$redis->delete($lastKey);
}
/**
* {#inheritdoc}
*
* #param string $prefix
* #return array
*/
public function queryKeys($prefix = null)
{
$redis = $this->getRedis();
$pattern = "{$this->_prefix}" . ($prefix ? $prefix : '') . '*';
return $redis->keys($pattern);
}
/**
* {#inheritdoc}
*
* #param string $keyName
* #param string $lifetime
* #return bool
*/
public function exists($keyName = null, $lifetime = null)
{
$redis = $this->getRedis();
if ($keyName === null) {
$lastKey = $this->_lastKey;
} else {
$lastKey = $this->getKeyName($keyName);
}
return (bool)$redis->exists($lastKey);
}
/**
* {#inheritdoc}
*
* #param string $keyName
* #param int $value
* #return int
*/
public function increment($keyName = null, $value = 1)
{
$redis = $this->getRedis();
if ($keyName === null) {
$lastKey = $this->_lastKey;
} else {
$lastKey = $this->getKeyName($keyName);
}
return $redis->incrBy($lastKey, $value);
}
/**
* {#inheritdoc}
*
* #param string $keyName
* #param int $value
* #return int
*/
public function decrement($keyName = null, $value = 1)
{
$redis = $this->getRedis();
if ($keyName === null) {
$lastKey = $this->_lastKey;
} else {
$lastKey = $this->getKeyName($keyName);
}
return $redis->decrBy($lastKey, $value);
}
/**
* {#inheritdoc}
*
* #return bool
*/
public function flush()
{
}
/**
* Get Prefix
*
* #return string
*/
public function getPrefix()
{
return $this->_prefix;
}
/**
* Get Redis Connection
*
* #return \Redis
*/
public function getRedis()
{
$redis = $this->_redis;
if (!is_object($redis)) {
$this->_connect();
$redis = $this->_redis;
}
return $redis;
}
/**
* Get Key Name
*
* #param $keyName
* #return string
*/
protected function getKeyName($keyName)
{
return $this->_prefix . $keyName;
}
}
Fore example exif_imagetype() works fine
<?php echo exif_imagetype('http://orig01.deviantart.net/ace1/f/2010/227/4/6/png_test_by_destron23.png');
But finfo_file() does not work.
<?php echo finfo_file(finfo_open(FILEINFO_MIME), 'http://orig01.deviantart.net/ace1/f/2010/227/4/6/png_test_by_destron23.png');
and got
Warning: finfo_file(): Failed identify data 0:(null) in /test.php on line 1
Any thoughts?
It is need to stream_wrapper_register()
class MimeStreamWrapper
{
const WRAPPER_NAME = 'mime';
/**
* #var resource
*/
public $context;
/**
* #var bool
*/
private static $isRegistered = false;
/**
* #var callable
*/
private $callBackFunction;
/**
* #var bool
*/
private $eof = false;
/**
* #var resource
*/
private $fp;
/**
* #var string
*/
private $path;
/**
* #var array
*/
private $fileStat;
/**
* #return array
*/
private function getStat()
{
if ($fStat = fstat($this->fp)) {
return $fStat;
}
$size = 100;
if ($headers = get_headers($this->path, true)) {
$head = array_change_key_case($headers, CASE_LOWER);
$size = (int)$head['content-length'];
}
$blocks = ceil($size / 512);
return array(
'dev' => 16777220,
'ino' => 15764,
'mode' => 33188,
'nlink' => 1,
'uid' => 10000,
'gid' => 80,
'rdev' => 0,
'size' => $size,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 4096,
'blocks' => $blocks,
);
}
/**
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
$this->fp = fopen($this->path, 'rb') or die('Cannot open file: ' . $this->path);
$this->fileStat = $this->getStat();
}
/**
* #param int $count
* #return string
*/
public function read($count) {
return fread($this->fp, $count);
}
/**
* #return string
*/
public function getStreamPath()
{
return str_replace(array('ftp://', 'http://', 'https://'), self::WRAPPER_NAME . '://', $this->path);
}
/**
* #return resource
*/
public function getContext()
{
if (!self::$isRegistered) {
stream_wrapper_register(self::WRAPPER_NAME, get_class());
self::$isRegistered = true;
}
return stream_context_create(
array(
self::WRAPPER_NAME => array(
'cb' => array($this, 'read'),
'fileStat' => $this->fileStat,
)
)
);
}
/**
* #param $path
* #param $mode
* #param $options
* #param $opened_path
* #return bool
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
if (!preg_match('/^r[bt]?$/', $mode) || !$this->context) {
return false;
}
$opt = stream_context_get_options($this->context);
if (!is_array($opt[self::WRAPPER_NAME]) ||
!isset($opt[self::WRAPPER_NAME]['cb']) ||
!is_callable($opt[self::WRAPPER_NAME]['cb'])
) {
return false;
}
$this->callBackFunction = $opt[self::WRAPPER_NAME]['cb'];
$this->fileStat = $opt[self::WRAPPER_NAME]['fileStat'];
return true;
}
/**
* #param int $count
* #return mixed|string
*/
public function stream_read($count)
{
if ($this->eof || !$count) {
return '';
}
if (($s = call_user_func($this->callBackFunction, $count)) == '') {
$this->eof = true;
}
return $s;
}
/**
* #return bool
*/
public function stream_eof()
{
return $this->eof;
}
/**
* #return array
*/
public function stream_stat()
{
return $this->fileStat;
}
/**
* #param int $castAs
*
* #return resource
*/
public function stream_cast($castAs)
{
$read = null;
$write = null;
$except = null;
return #stream_select($read, $write, $except, $castAs);
}
}
//$path = 'ftp://ftp.happy.kiev.ua/pub/pictures/ksyu/15.TIF';
//$path = 'ftp://ftp.happy.kiev.ua/pub/pictures/arcwolf1.gif';
//$path = 'ftp://ftp.happy.kiev.ua/pub/pictures/pub_k.pcx';
//$path = 'ftp://ftp.happy.kiev.ua/pub/pictures/9427.jpg';
//$path = 'https://homepages.cae.wisc.edu/~ece533/images/airplane.png';
//$path = '/etc/hosts';
$path = 'http://fc04.deviantart.net/fs71/f/2010/227/4/6/PNG_Test_by_Destron23.png';
echo "File: ", $path, "\n";
$wrapper = new MimeStreamWrapper();
$wrapper->setPath($path);
$fInfo = new finfo(FILEINFO_MIME);
echo "MIME-type: ", $fInfo->file($wrapper->getStreamPath(), FILEINFO_MIME_TYPE, $wrapper->getContext()), "\n";
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 get the following error when I try to load views from controllers that use the 'UploadPack' - (source:- https://github.com/szajbus/uploadpack):-
Strict (2048): Declaration of UploadBehavior::setup() should be compatible with ModelBehavior::setup(Model $model, $config = Array) [APP\Plugin\upload_pack\Model\Behavior\UploadBehavior.php, line 15]
My 'UploadBehavior.php' and 'ModelBehavior.php' files are below, has anyone got any ideas of how to resolve this error?
<?php
App::uses('HttpSocket', 'Network/Http');
/**
* This file is a part of UploadPack - a plugin that makes file uploads in CakePHP as easy as possible.
*
* UploadBehavior
*
* UploadBehavior does all the job of saving files to disk while saving records to database. For more info read UploadPack documentation.
*
* joe bartlett's lovingly handcrafted tweaks add several resize modes. see "more on styles" in the documentation.
*
* #author Michał Szajbe (michal.szajbe#gmail.com) and joe bartlett (contact#jdbartlett.com)
* #link http://github.com/szajbus/uploadpack
*/
class UploadBehavior extends ModelBehavior {
private static $__settings = array();
private $toWrite = array();
private $toDelete = array();
private $maxWidthSize = false;
public function setup(&$model, $settings = array()) {
$defaults = array(
'path' => ':webroot/upload/:model/:id/:basename_:style.:extension',
'styles' => array(),
'resizeToMaxWidth' => false,
'quality' => 75
);
foreach ($settings as $field => $array) {
self::$__settings[$model->name][$field] = array_merge($defaults, $array);
}
}
public function beforeSave(&$model) {
$this->_reset();
foreach (self::$__settings[$model->name] as $field => $settings) {
if (!empty($model->data[$model->name][$field]) && is_array($model->data[$model->name][$field]) && file_exists($model->data[$model->name][$field]['tmp_name'])) {
if (!empty($model->id)) {
$this->_prepareToDeleteFiles($model, $field, true);
}
$this->_prepareToWriteFiles($model, $field);
unset($model->data[$model->name][$field]);
$model->data[$model->name][$field.'_file_name'] = $this->toWrite[$field]['name'];
$model->data[$model->name][$field.'_file_size'] = $this->toWrite[$field]['size'];
$model->data[$model->name][$field.'_content_type'] = $this->toWrite[$field]['type'];
} elseif (array_key_exists($field, $model->data[$model->name]) && $model->data[$model->name][$field] === null) {
if (!empty($model->id)) {
$this->_prepareToDeleteFiles($model, $field, true);
}
unset($model->data[$model->name][$field]);
$model->data[$model->name][$field.'_file_name'] = null;
$model->data[$model->name][$field.'_file_size'] = null;
$model->data[$model->name][$field.'_content_type'] = null;
}
}
return true;
}
public function afterSave(&$model, $create) {
if (!$create) {
$this->_deleteFiles($model);
}
$this->_writeFiles($model);
}
public function beforeDelete(&$model) {
$this->_reset();
$this->_prepareToDeleteFiles($model);
return true;
}
public function afterDelete(&$model) {
$this->_deleteFiles($model);
}
public function beforeValidate(&$model) {
foreach (self::$__settings[$model->name] as $field => $settings) {
if (isset($model->data[$model->name][$field])) {
$data = $model->data[$model->name][$field];
if ((empty($data) || is_array($data) && empty($data['tmp_name'])) && !empty($settings['urlField']) && !empty($model->data[$model->name][$settings['urlField']])) {
$data = $model->data[$model->name][$settings['urlField']];
}
if (!is_array($data)) {
$model->data[$model->name][$field] = $this->_fetchFromUrl($data);
}
}
}
return true;
}
private function _reset() {
$this->toWrite = null;
$this->toDelete = null;
}
private function _fetchFromUrl($url) {
$data = array('remote' => true);
$data['name'] = end(explode('/', $url));
$data['tmp_name'] = tempnam(sys_get_temp_dir(), $data['name']) . '.' . end(explode('.', $url));
$httpSocket = new HttpSocket();
$raw = $httpSocket->get($url);
$response = $httpSocket->response;
$data['size'] = strlen($raw);
$data['type'] = reset(explode(';', $response['header']['Content-Type']));
file_put_contents($data['tmp_name'], $raw);
return $data;
}
private function _prepareToWriteFiles(&$model, $field) {
$this->toWrite[$field] = $model->data[$model->name][$field];
// make filename URL friendly by using Cake's Inflector
$this->toWrite[$field]['name'] =
Inflector::slug(substr($this->toWrite[$field]['name'], 0, strrpos($this->toWrite[$field]['name'], '.'))). // filename
substr($this->toWrite[$field]['name'], strrpos($this->toWrite[$field]['name'], '.')); // extension
}
private function _writeFiles(&$model) {
if (!empty($this->toWrite)) {
foreach ($this->toWrite as $field => $toWrite) {
$settings = $this->_interpolate($model, $field, $toWrite['name'], 'original');
$destDir = dirname($settings['path']);
if (!file_exists($destDir)) {
#mkdir($destDir, 0777, true);
#chmod($destDir, 0777);
}
if (is_dir($destDir) && is_writable($destDir)) {
$move = !empty($toWrite['remote']) ? 'rename' : 'move_uploaded_file';
if (#$move($toWrite['tmp_name'], $settings['path'])) {
if($this->maxWidthSize) {
$this->_resize($settings['path'], $settings['path'], $this->maxWidthSize.'w', $settings['quality']);
}
foreach ($settings['styles'] as $style => $geometry) {
$newSettings = $this->_interpolate($model, $field, $toWrite['name'], $style);
$this->_resize($settings['path'], $newSettings['path'], $geometry, $settings['quality']);
}
}
}
}
}
}
private function _prepareToDeleteFiles(&$model, $field = null, $forceRead = false) {
$needToRead = true;
if ($field === null) {
$fields = array_keys(self::$__settings[$model->name]);
foreach ($fields as &$field) {
$field .= '_file_name';
}
} else {
$field .= '_file_name';
$fields = array($field);
}
if (!$forceRead && !empty($model->data[$model->alias])) {
$needToRead = false;
foreach ($fields as $field) {
if (!array_key_exists($field, $model->data[$model->alias])) {
$needToRead = true;
break;
}
}
}
if ($needToRead) {
$data = $model->find('first', array('conditions' => array($model->alias.'.'.$model->primaryKey => $model->id), 'fields' => $fields, 'callbacks' => false));
} else {
$data = $model->data;
}
if (is_array($this->toDelete)) {
$this->toDelete = array_merge($this->toDelete, $data[$model->alias]);
} else {
$this->toDelete = $data[$model->alias];
}
$this->toDelete['id'] = $model->id;
}
private function _deleteFiles(&$model) {
foreach (self::$__settings[$model->name] as $field => $settings) {
if (!empty($this->toDelete[$field.'_file_name'])) {
$styles = array_keys($settings['styles']);
$styles[] = 'original';
foreach ($styles as $style) {
$settings = $this->_interpolate($model, $field, $this->toDelete[$field.'_file_name'], $style);
if (file_exists($settings['path'])) {
#unlink($settings['path']);
}
}
}
}
}
private function _interpolate(&$model, $field, $filename, $style) {
return self::interpolate($model->name, $model->id, $field, $filename, $style);
}
static public function interpolate($modelName, $modelId, $field, $filename, $style = 'original', $defaults = array()) {
$pathinfo = UploadBehavior::_pathinfo($filename);
$interpolations = array_merge(array(
'app' => preg_replace('/\/$/', '', APP),
'webroot' => preg_replace('/\/$/', '', WWW_ROOT),
'model' => Inflector::tableize($modelName),
'basename' => !empty($filename) ? $pathinfo['filename'] : null,
'extension' => !empty($filename) ? $pathinfo['extension'] : null,
'id' => $modelId,
'style' => $style,
'attachment' => Inflector::pluralize($field),
'hash' => md5((!empty($filename) ? $pathinfo['filename'] : "") . Configure::read('Security.salt'))
), $defaults);
$settings = self::$__settings[$modelName][$field];
$keys = array('path', 'url', 'default_url');
foreach ($interpolations as $k => $v) {
foreach ($keys as $key) {
if (isset($settings[$key])) {
$settings[$key] = preg_replace('/\/{2,}/', '/', str_replace(":$k", $v, $settings[$key]));
}
}
}
return $settings;
}
static private function _pathinfo($filename) {
$pathinfo = pathinfo($filename);
// PHP < 5.2.0 doesn't include 'filename' key in pathinfo. Let's try to fix this.
if (empty($pathinfo['filename'])) {
$suffix = !empty($pathinfo['extension']) ? '.'.$pathinfo['extension'] : '';
$pathinfo['filename'] = basename($pathinfo['basename'], $suffix);
}
return $pathinfo;
}
private function _resize($srcFile, $destFile, $geometry, $quality = 75) {
copy($srcFile, $destFile);
#chmod($destFile, 0777);
$pathinfo = UploadBehavior::_pathinfo($srcFile);
$src = null;
$createHandler = null;
$outputHandler = null;
switch (strtolower($pathinfo['extension'])) {
case 'gif':
$createHandler = 'imagecreatefromgif';
$outputHandler = 'imagegif';
break;
case 'jpg':
case 'jpeg':
$createHandler = 'imagecreatefromjpeg';
$outputHandler = 'imagejpeg';
break;
case 'png':
$createHandler = 'imagecreatefrompng';
$outputHandler = 'imagepng';
$quality = null;
break;
default:
return false;
}
if ($src = $createHandler($destFile)) {
$srcW = imagesx($src);
$srcH = imagesy($src);
// determine destination dimensions and resize mode from provided geometry
if (preg_match('/^\\[[\\d]+x[\\d]+\\]$/', $geometry)) {
// resize with banding
list($destW, $destH) = explode('x', substr($geometry, 1, strlen($geometry)-2));
$resizeMode = 'band';
} elseif (preg_match('/^[\\d]+x[\\d]+$/', $geometry)) {
// cropped resize (best fit)
list($destW, $destH) = explode('x', $geometry);
$resizeMode = 'best';
} elseif (preg_match('/^[\\d]+w$/', $geometry)) {
// calculate heigh according to aspect ratio
$destW = (int)$geometry-1;
$resizeMode = false;
} elseif (preg_match('/^[\\d]+h$/', $geometry)) {
// calculate width according to aspect ratio
$destH = (int)$geometry-1;
$resizeMode = false;
} elseif (preg_match('/^[\\d]+l$/', $geometry)) {
// calculate shortest side according to aspect ratio
if ($srcW > $srcH) $destW = (int)$geometry-1;
else $destH = (int)$geometry-1;
$resizeMode = false;
}
if (!isset($destW)) $destW = ($destH/$srcH) * $srcW;
if (!isset($destH)) $destH = ($destW/$srcW) * $srcH;
// determine resize dimensions from appropriate resize mode and ratio
if ($resizeMode == 'best') {
// "best fit" mode
if ($srcW > $srcH) {
if ($srcH/$destH > $srcW/$destW) $ratio = $destW/$srcW;
else $ratio = $destH/$srcH;
} else {
if ($srcH/$destH < $srcW/$destW) $ratio = $destH/$srcH;
else $ratio = $destW/$srcW;
}
$resizeW = $srcW*$ratio;
$resizeH = $srcH*$ratio;
}
elseif ($resizeMode == 'band') {
// "banding" mode
if ($srcW > $srcH) $ratio = $destW/$srcW;
else $ratio = $destH/$srcH;
$resizeW = $srcW*$ratio;
$resizeH = $srcH*$ratio;
}
else {
// no resize ratio
$resizeW = $destW;
$resizeH = $destH;
}
$img = imagecreatetruecolor($destW, $destH);
imagefill($img, 0, 0, imagecolorallocate($img, 255, 255, 255));
imagecopyresampled($img, $src, ($destW-$resizeW)/2, ($destH-$resizeH)/2, 0, 0, $resizeW, $resizeH, $srcW, $srcH);
$outputHandler($img, $destFile, $quality);
return true;
}
return false;
}
public function attachmentMinSize(&$model, $value, $min) {
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return (int)$min <= (int)$value['size'];
}
return true;
}
public function attachmentMaxSize(&$model, $value, $max) {
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return (int)$value['size'] <= (int)$max;
}
return true;
}
public function attachmentContentType(&$model, $value, $contentTypes) {
$value = array_shift($value);
if (!is_array($contentTypes)) {
$contentTypes = array($contentTypes);
}
if (!empty($value['tmp_name'])) {
foreach ($contentTypes as $contentType) {
if (substr($contentType, 0, 1) == '/') {
if (preg_match($contentType, $value['type'])) {
return true;
}
} elseif ($contentType == $value['type']) {
return true;
}
}
return false;
}
return true;
}
public function attachmentPresence(&$model, $value) {
$keys = array_keys($value);
$field = $keys[0];
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return true;
}
if (!empty($model->id)) {
if (!empty($model->data[$model->alias][$field.'_file_name'])) {
return true;
} elseif (!isset($model->data[$model->alias][$field.'_file_name'])) {
$existingFile = $model->field($field.'_file_name', array($model->primaryKey => $model->id));
if (!empty($existingFile)) {
return true;
}
}
}
return false;
}
public function minWidth(&$model, $value, $minWidth) {
return $this->_validateDimension($value, 'min', 'x', $minWidth);
}
public function minHeight(&$model, $value, $minHeight) {
return $this->_validateDimension($value, 'min', 'y', $minHeight);
}
public function maxWidth(&$model, $value, $maxWidth) {
$keys = array_keys($value);
$field = $keys[0];
$settings = self::$__settings[$model->name][$field];
if($settings['resizeToMaxWidth'] && !$this->_validateDimension($value, 'max', 'x', $maxWidth)) {
$this->maxWidthSize = $maxWidth;
return true;
} else {
return $this->_validateDimension($value, 'max', 'x', $maxWidth);
}
}
public function maxHeight(&$model, $value, $maxHeight) {
return $this->_validateDimension($value, 'max', 'y', $maxHeight);
}
private function _validateDimension($upload, $mode, $axis, $value) {
$upload = array_shift($upload);
$func = 'images'.$axis;
if(!empty($upload['tmp_name'])) {
$createHandler = null;
if($upload['type'] == 'image/jpeg') {
$createHandler = 'imagecreatefromjpeg';
} else if($upload['type'] == 'image/gif') {
$createHandler = 'imagecreatefromgif';
} else if($upload['type'] == 'image/png') {
$createHandler = 'imagecreatefrompng';
} else {
return false;
}
if($img = $createHandler($upload['tmp_name'])) {
switch ($mode) {
case 'min':
return $func($img) >= $value;
break;
case 'max':
return $func($img) <= $value;
break;
}
}
}
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//My 'ModelBehavior' file:-
<?php
/**
* Model behaviors base class.
*
* Adds methods and automagic functionality to Cake Models.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* #link http://cakephp.org CakePHP(tm) Project
* #package Cake.Model
* #since CakePHP(tm) v 1.2.0.0
* #license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
/**
* Model behavior base class.
*
* Defines the Behavior interface, and contains common model interaction functionality. Behaviors
* allow you to simulate mixins, and create reusable blocks of application logic, that can be reused across
* several models. Behaviors also provide a way to hook into model callbacks and augment their behavior.
*
* ### Mixin methods
*
* Behaviors can provide mixin like features by declaring public methods. These methods should expect
* the model instance to be shifted onto the parameter list.
*
* {{{
* function doSomething(Model $model, $arg1, $arg2) {
* //do something
* }
* }}}
*
* Would be called like `$this->Model->doSomething($arg1, $arg2);`.
*
* ### Mapped methods
*
* Behaviors can also define mapped methods. Mapped methods use pattern matching for method invocation. This
* allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to
* be declared in your behaviors `$mapMethods` array. The method signature for a mapped method is slightly different
* than a normal behavior mixin method.
*
* {{{
* public $mapMethods = array('/do(\w+)/' => 'doSomething');
*
* function doSomething(Model $model, $method, $arg1, $arg2) {
* //do something
* }
* }}}
*
* The above will map every doXXX() method call to the behavior. As you can see, the model is
* still the first parameter, but the called method name will be the 2nd parameter. This allows
* you to munge the method name for additional information, much like Model::findAllByXX.
*
* #package Cake.Model
* #see Model::$actsAs
* #see BehaviorCollection::load()
*/
class ModelBehavior extends Object {
/**
* Contains configuration settings for use with individual model objects. This
* is used because if multiple models use this Behavior, each will use the same
* object instance. Individual model settings should be stored as an
* associative array, keyed off of the model name.
*
* #var array
* #see Model::$alias
*/
public $settings = array();
/**
* Allows the mapping of preg-compatible regular expressions to public or
* private methods in this class, where the array key is a /-delimited regular
* expression, and the value is a class method. Similar to the functionality of
* the findBy* / findAllBy* magic methods.
*
* #var array
*/
public $mapMethods = array();
/**
* Setup this behavior with the specified configuration settings.
*
* #param Model $model Model using this behavior
* #param array $config Configuration settings for $model
* #return void
*/
public function setup(Model $model, $config = array()) {
}
/**
* Clean up any initialization this behavior has done on a model. Called when a behavior is dynamically
* detached from a model using Model::detach().
*
* #param Model $model Model using this behavior
* #return void
* #see BehaviorCollection::detach()
*/
public function cleanup(Model $model) {
if (isset($this->settings[$model->alias])) {
unset($this->settings[$model->alias]);
}
}
/**
* beforeFind can be used to cancel find operations, or modify the query that will be executed.
* By returning null/false you can abort a find. By returning an array you can modify/replace the query
* that is going to be run.
*
* #param Model $model Model using this behavior
* #param array $query Data used to execute this query, i.e. conditions, order, etc.
* #return boolean|array False or null will abort the operation. You can return an array to replace the
* $query that will be eventually run.
*/
public function beforeFind(Model $model, $query) {
return true;
}
/**
* After find callback. Can be used to modify any results returned by find.
*
* #param Model $model Model using this behavior
* #param mixed $results The results of the find operation
* #param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
* #return mixed An array value will replace the value of $results - any other value will be ignored.
*/
public function afterFind(Model $model, $results, $primary) {
}
/**
* beforeValidate is called before a model is validated, you can use this callback to
* add behavior validation rules into a models validate array. Returning false
* will allow you to make the validation fail.
*
* #param Model $model Model using this behavior
* #return mixed False or null will abort the operation. Any other result will continue.
*/
public function beforeValidate(Model $model) {
return true;
}
/**
* afterValidate is called just after model data was validated, you can use this callback
* to perform any data cleanup or preparation if needed
*
* #param Model $model Model using this behavior
* #return mixed False will stop this event from being passed to other behaviors
*/
public function afterValidate(Model $model) {
return true;
}
/**
* beforeSave is called before a model is saved. Returning false from a beforeSave callback
* will abort the save operation.
*
* #param Model $model Model using this behavior
* #return mixed False if the operation should abort. Any other result will continue.
*/
public function beforeSave(Model $model) {
return true;
}
/**
* afterSave is called after a model is saved.
*
* #param Model $model Model using this behavior
* #param boolean $created True if this save created a new record
* #return boolean
*/
public function afterSave(Model $model, $created) {
return true;
}
/**
* Before delete is called before any delete occurs on the attached model, but after the model's
* beforeDelete is called. Returning false from a beforeDelete will abort the delete.
*
* #param Model $model Model using this behavior
* #param boolean $cascade If true records that depend on this record will also be deleted
* #return mixed False if the operation should abort. Any other result will continue.
*/
public function beforeDelete(Model $model, $cascade = true) {
return true;
}
/**
* After delete is called after any delete occurs on the attached model.
*
* #param Model $model Model using this behavior
* #return void
*/
public function afterDelete(Model $model) {
}
/**
* DataSource error callback
*
* #param Model $model Model using this behavior
* #param string $error Error generated in DataSource
* #return void
*/
public function onError(Model $model, $error) {
}
/**
* If $model's whitelist property is non-empty, $field will be added to it.
* Note: this method should *only* be used in beforeValidate or beforeSave to ensure
* that it only modifies the whitelist for the current save operation. Also make sure
* you explicitly set the value of the field which you are allowing.
*
* #param Model $model Model using this behavior
* #param string $field Field to be added to $model's whitelist
* #return void
*/
protected function _addToWhitelist(Model $model, $field) {
if (is_array($field)) {
foreach ($field as $f) {
$this->_addToWhitelist($model, $f);
}
return;
}
if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) {
$model->whitelist[] = $field;
}
}
}
The signature of the setup() method of the UploadBehavior should be the same as the signature of the setup() method in the parent class. This means you have to change: public function setup(&$model, $settings = array()) in your UploadBehavior to public function setup(Model $model, $settings = array()) and the error should go away.
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;
}
}