From 2a9014e4c644b1b9001ab47a7339da4e64e93bc7 Mon Sep 17 00:00:00 2001 From: Hisateru Tanaka Date: Fri, 3 Jun 2011 12:59:42 +0900 Subject: [PATCH] Pinoco upgraded to 0.4.0 --- _app/lib/Pinoco.php | 224 +++++++++++++++++++----- _app/lib/Pinoco/FlowControl.php | 19 ++- _app/lib/Pinoco/MIMEType.php | 11 +- _app/lib/Pinoco/PDOWrapper.php | 290 ++++++++++++++++++++++++++++++++ _app/lib/Pinoco/Renderer.php | 4 +- _app/lib/Pinoco/VarsList.php | 204 ++++++++++++++++++---- 6 files changed, 664 insertions(+), 88 deletions(-) create mode 100644 _app/lib/Pinoco/PDOWrapper.php diff --git a/_app/lib/Pinoco.php b/_app/lib/Pinoco.php index 41f0a2f..eb737b0 100644 --- a/_app/lib/Pinoco.php +++ b/_app/lib/Pinoco.php @@ -9,8 +9,8 @@ * @package Pinoco * @author Hisateru Tanaka * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License - * @version 0.3.0 - * @link http://code.google.com/p/pinoco/ + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco * @filesource */ @@ -47,25 +47,25 @@ * * * @package Pinoco - * @property-read string $baseuri - * @property-read string $basedir - * @property-read string $sysdir - * @property Pinoco_List $incdir - * @property-read string $path - * @property-read string $script - * @property-read Pinoco_List $activity - * @property-read string $subpath - * @property-read Pinoco_List $pathargs - * @property string $directory_index - * @property string $page - * @property-read Pinoco_Vars $renderers - * @property-read Pinoco_Vars $autolocal - * @property callback $url_modifier - * @property callback $page_modifier + * @property-read string $baseuri Base URI + * @property-read string $basedir Base directory + * @property-read string $sysdir Application directory + * @property Pinoco_List $incdir Include pathes + * @property-read string $path Path under base URI + * @property-read string $script Current hook script + * @property-read Pinoco_List $activity Activity history of hook scripts + * @property-read string $subpath Sub path under current hook script + * @property-read Pinoco_List $pathargs Path elements matches _default[.*] hooks + * @property string $directory_index Space separated directory index files(like Apache) + * @property string $page Template file to be rendered + * @property-read Pinoco_Vars $renderers File extension to rendering module mappings + * @property-read Pinoco_Vars $autolocal Auto extraced variables into local scope + * @property callback $url_modifier URL modification callback + * @property callback $page_modifier Template page base path modification callback */ class Pinoco extends Pinoco_DynamicVars { - const VERSION = "0.3.0"; + const VERSION = "0.4.0"; private $_baseuri; // R gateway index.php location on internet private $_basedir; // R gateway index.php location on file system @@ -289,7 +289,7 @@ public static function wrapList(&$ref) * @param mixed $args,... * @return object */ - public static function newObj($class) + public static function newObj($class /*[, $args[, ...]]*/) { $seppos = strrpos($class, '/'); if($seppos !== FALSE) { @@ -319,6 +319,20 @@ public static function newObj($class) } } + /** + * Wrapped PDO factory + * @param string $dsn + * @param string $un + * @param string $pw + * @param array $opts + * @return Pinoco_PDOWrapper + */ + public static function newPDOWrapper($dsn, $un="", $pw="", $opts=array()) + { + require_once dirname(__FILE__) . '/Pinoco/PDOWrapper.php'; + return new Pinoco_PDOWrapper($dsn, $un, $pw, $opts); + } + /** * It reads and executes another PHP file with any local variables. * It can read already executed file. @@ -775,7 +789,6 @@ public static function __callStatic($name, $arguments) // runtime core /** * Writes Pinoco credit into HTTP header. - * @internal * @return void */ private function _credit_into_x_powerd_by() @@ -847,12 +860,124 @@ public function updateIncdir() ini_set('include_path', implode($sep, $runinc)); } + + /** + * @param string $errno + * @param string $errstr + * @param string $errfile + * @param string $errline + * @return void + * @ignore + */ + public function _error_handler($errno, $errstr, $errfile, $errline) + { + if(function_exists('xdebug_is_enabled') && xdebug_is_enabled()) { + return FALSE; + } + if((error_reporting() & $errno) == 0) { + return FALSE; + } + + $errno2txt = array( + E_NOTICE=>"Notice", E_USER_NOTICE=>"Notice", + E_WARNING=>"Warning", E_USER_WARNING=>"Warning", + E_ERROR=>"Fatal Error", E_USER_ERROR=>"Fatal Error" + ); + $errors = isset($errno2txt[$errno]) ? $errno2txt[$errno] : "Unknown"; + + $trace = debug_backtrace(); + array_shift($trace); + $stacktrace = array(); + for($i=0; $i < count($trace); $i++) { + $stacktrace[] = htmlspecialchars(sprintf("#%d %s%s%s called at [%s:%d]", + $i, + @$trace[$i]['class'], + @$trace[$i]['type'], + @$trace[$i]['function'], + @$trace[$i]['file'], + @$trace[$i]['line'] + )); + } + + ob_start(); + if (ini_get("display_errors")) { + printf("
\n%s: %s in %s on line %d
\n", $errors, $errstr, $errfile, $errline); + echo "Stack trace:
\n" . implode("
\n", $stacktrace) . "

\n"; + } + if (ini_get('log_errors')) { + error_log(sprintf("PHP %s: %s in %s on line %d", $errors, $errstr, $errfile, $errline)); + } + if($errno & (E_ERROR | E_USER_ERROR)) { + if (!headers_sent()) { + header('HTTP/1.0 500 Fatal Error'); + header('Content-Type:text/html'); + } + } + ob_end_flush(); + if($errno & (E_ERROR | E_USER_ERROR)) { + echo str_repeat(' ', 100)."\n"; // IE won't display error pages < 512b + exit(1); + } + return TRUE; + } + + /** + * @param Exception $e + * @return void + * @ignore + */ + public function _exception_handler($e) + { + if (!headers_sent()) { + header('HTTP/1.0 500 Uncaught Exception'); + header('Content-Type:text/html'); + } + + $line = $e->getFile(); + if ($e->getLine()) { + $line .= ' line '.$e->getLine(); + } + + if (ini_get('display_errors')) { + $title = '500 ' . get_class($e); + $body = "

\n ".htmlspecialchars($e->getMessage()).'

' . + '

In '.htmlspecialchars($line)."

\n".htmlspecialchars($e->getTraceAsString()).'
'; + } else { + $title = "500 Uncaught Exception"; + $body = "

The server encountered an uncaught exception and was unable to complete your request.

"; + } + + if (ini_get('log_errors')) { + error_log($e->getMessage().' in '.$line); + } + + if(ob_get_level() > 0 && ob_get_length() > 0) { + $curbuf = ob_get_contents(); + ob_clean(); + } + if(isset($curbuf) && preg_match('/"; + echo "

" . $title . "

\n" . $body . ''; + } + else { + echo '' . "\n"; + echo "\n".$title."\n\n"; + if(isset($curbuf)) { + echo $curbuf; + echo "
"; + } + echo "

" . $title . "

\n" . $body . ''; + } + echo str_repeat(' ', 100)."\n"; // IE won't display error pages < 512b + exit(1); + } + /** * Runs a hook script. * @param string $script * @param string $subpath * @return bool - * @internal */ private function _run_hook_if_exists($script, $subpath) { @@ -891,19 +1016,15 @@ public function run($output_buffering=TRUE) // insert credit into X-Powered-By header $this->_credit_into_x_powerd_by(); - // non-html but existing => raw binary with mime-type header - $realfile = $this->_basedir . $this->_path; - if(!$this->isRenderablePath($this->_path) && is_file($realfile)) { - header('Content-Type:' . $this->mimeType($realfile)); - $st = stat($realfile); - header('Last-Modified:' . str_replace('+0000', 'GMT', gmdate("r", $st['mtime']))); - readfile($realfile); // TODO: streaming - return; - } - self::$_current_instance = $this; - //set_error_handler(array($this, "_exception_error_handler")); + if(!(function_exists('xdebug_is_enabled') && xdebug_is_enabled()) + && PHP_SAPI !== 'cli') + { + $special_error_handler_enabled = true; + set_error_handler(array($this, "_error_handler")); + set_exception_handler(array($this, "_exception_handler")); + } if($output_buffering) { ob_start(); @@ -1018,17 +1139,36 @@ public function run($output_buffering=TRUE) //render if(!$this->_manually_rendered && $this->_page) { - $page = $this->_page_from_path_with_directory_index( - ($this->_page != "") ? $this->resolvePath($this->_page) : $this->_path); - - $this->updateIncdir(); - if($this->_page_modifier) { - $page = call_user_func($this->_page_modifier, $page, $this->_path); + if($this->_page != "") { + $pagepath = $this->resolvePath($this->_page); + $page = $this->_page_from_path_with_directory_index($pagepath); + } + else { + $pagepath = $this->_path; + if($this->_page_modifier) { + $this->updateIncdir(); + $pagepath = call_user_func($this->_page_modifier, $pagepath); + } + if($pagepath) { + $page = $this->_page_from_path_with_directory_index($pagepath); + } + else { + $page = FALSE; + } } if($page && is_file($this->_basedir . $page)) { - $this->render($page); + // non-html but existing => raw binary with mime-type header + if($this->isRenderablePath($this->_basedir . $page)) { + $this->render($page); + } + else { + header('Content-Type:' . $this->mimeType($this->_basedir . $page)); + //$st = stat($this->_basedir . $page); + //header('Last-Modified:' . str_replace('+0000', 'GMT', gmdate("r", $st['mtime']))); + readfile($this->_basedir . $page); // TODO: streaming + } } else if(!$proccessed) { // no page and no tarminal hook indicates resource was not found or forbidden @@ -1069,8 +1209,10 @@ public function run($output_buffering=TRUE) ob_end_flush(); } - //restore_error_handler(); - + if(isset($special_error_handler_enabled)) { + restore_exception_handler(); + restore_error_handler(); + } self::$_current_instance = null; } } diff --git a/_app/lib/Pinoco/FlowControl.php b/_app/lib/Pinoco/FlowControl.php index 2f13760..e4afa1f 100644 --- a/_app/lib/Pinoco/FlowControl.php +++ b/_app/lib/Pinoco/FlowControl.php @@ -9,14 +9,15 @@ * @package Pinoco * @author Hisateru Tanaka * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License - * @version 0.3.0 - * @link http://code.google.com/p/pinoco/ + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco * @filesource */ /** * Flow control object * @package Pinoco + * @internal */ class Pinoco_FlowControl extends Exception { } @@ -24,6 +25,7 @@ class Pinoco_FlowControl extends Exception { /** * Flow control object * @package Pinoco + * @internal */ class Pinoco_FlowControlSkip extends Pinoco_FlowControl { } @@ -31,6 +33,7 @@ class Pinoco_FlowControlSkip extends Pinoco_FlowControl { /** * Flow control object * @package Pinoco + * @internal */ class Pinoco_FlowControlTerminate extends Pinoco_FlowControl { } @@ -38,6 +41,7 @@ class Pinoco_FlowControlTerminate extends Pinoco_FlowControl { /** * Flow control object * @package Pinoco + * @internal */ class Pinoco_FlowControlHttpError extends Pinoco_FlowControl { @@ -57,7 +61,7 @@ public function __construct($code, $title=NULL, $message=NULL) } private function _code2message($code, $field) { - $ise = "The server encountered an internal error or misconfigurationand was unable to complete your request."; + $ise = "The server encountered an internal error or misconfiguration and was unable to complete your request."; if(!$this->_status_messages) { $this->_status_messages = array( 100 => array('title'=>'Continue', 'message'=>$ise), @@ -137,7 +141,9 @@ private function _code2message($code, $field) { */ public function respond($pinoco) { - header("HTTP/1.0 " . $this->code . " " . $this->title); + if(!headers_sent()) { + header("HTTP/1.0 " . $this->code . " " . $this->title); + } $pref = $pinoco->sysdir . "/error/"; foreach(array($this->code . '.php', 'default.php') as $errfile) { @@ -147,7 +153,9 @@ public function respond($pinoco) } } - header("Content-Type: text/html; charset=iso-8859-1"); + if(!headers_sent()) { + header("Content-Type: text/html; charset=iso-8859-1"); + } echo '' . "\n"; echo "\n"; echo "" . $this->code . " " . $this->title . "\n"; @@ -161,6 +169,7 @@ public function respond($pinoco) /** * Flow control object * @package Pinoco + * @internal */ class Pinoco_FlowControlHttpRedirect extends Pinoco_FlowControlHttpError { /** diff --git a/_app/lib/Pinoco/MIMEType.php b/_app/lib/Pinoco/MIMEType.php index 2a12733..c1410ce 100644 --- a/_app/lib/Pinoco/MIMEType.php +++ b/_app/lib/Pinoco/MIMEType.php @@ -9,8 +9,8 @@ * @package Pinoco * @author Hisateru Tanaka * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License - * @version 0.3.0 - * @link http://code.google.com/p/pinoco/ + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco * @filesource */ @@ -229,6 +229,11 @@ class Pinoco_MIMEType { public static function fromFileName($filename) { $ext = pathinfo($filename, PATHINFO_EXTENSION); - return $ext ? self::$EXT2TYPE[$ext] : ""; + if($ext && isset(self::$EXT2TYPE[$ext])) { + return self::$EXT2TYPE[$ext]; + } + else { + return 'application/octet-stream'; + } } } \ No newline at end of file diff --git a/_app/lib/Pinoco/PDOWrapper.php b/_app/lib/Pinoco/PDOWrapper.php new file mode 100644 index 0000000..5437458 --- /dev/null +++ b/_app/lib/Pinoco/PDOWrapper.php @@ -0,0 +1,290 @@ + + * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco + * @filesource + */ + +/** + */ +require_once dirname(__FILE__) . '/VarsList.php'; + +/** + * PDOWrapper provides extra methods to PDO. + * Of course you can use also PDO functions. + * @package Pinoco + * @property-read PDO $connection + * @property mixed $afterConnection + * @method bool beginTransaction() + * @method bool commit() + * @method mixed errorCode() + * @method array errorInfo() + * @method int exec() exec(string $statement) + * @method mixed getAttribute() getAttribute(int $attribute) + * @method array getAvailableDrivers() + * @method bool inTransaction() + * @method string lastInsertId() lastInsertId([ string $name = NULL ]) + * @method string quote() quote( string $string [, int $parameter_type = PDO::PARAM_STR ] ) + * @method bool rollBack() + * @method bool setAttribute() setAttribute( int $attribute , mixed $value ) + */ +class Pinoco_PDOWrapper { + private $_dsn; + private $_un; + private $_pw; + private $_opts; + private $_conn; + private $_after_connection; + + /** + * Wrapped PDO factory + * @param string $dsn + * @param string $un + * @param string $pw + * @param array $opts + * @return Pinoco_PDOWrapper + */ + public static function newInstance($dsn, $un="", $pw="", $opts=array()) + { + return new Pinoco_PDOWrapper($dsn, $un, $pw, $opts); + } + + /** + * @param string $dsn + * @param string $un + * @param string $pw + * @param array $opts + */ + public function __construct($dsn, $un="", $pw="", $opts=array()) + { + $this->_dsn = $dsn; + $this->_un = $un; + $this->_pw = $pw; + $this->_opts = $opts; + $this->_conn = NULL; + $this->_after_connection = false; + } + + /** + * @return mixed + */ + public function getAfterConnection() + { + return $this->_after_connection; + } + + /** + * @param mixed $after_connection + */ + public function setAfterConnection($after_connection) + { + $this->_after_connection = $after_connection; + } + + /** + * @return PDO + */ + public function getConnection() + { + if($this->_conn === NULL) { + $this->_conn = new PDO($this->_dsn, $this->_un, $this->_pw, $this->_opts); + $this->_conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if($this->_after_connection) { + if(is_callable($this->_after_connection)) { + call_user_func($this->_after_connection, $this); + } + else { + $this->execute($this->_after_connection); + } + } + } + return $this->_conn; + } + + public function __call($name, $args) + { + return call_user_func_array(array($this->getConnection(), $name), $args); + } + + /** + * This method provides wrapped prepared statement. + * @param string $sql + * @param array $opts + * @return Pinoco_PDOStatementWrapper + */ + public function prepare($sql, $opts=array()) + { + return new Pinoco_PDOStatementWrapper( + $this->getConnection()->prepare($sql, $opts) + ); + } + + /** + * Alias to exec + * @param mixed $args... + * @return int + */ + public function execute(/*[$args[, ...]]*/) + { + $args = func_get_args(); + return call_user_func_array(array($this, 'exec'), $args); + } + + /** + * This method provides wrapped statement already query sent. + * @param string $sql + * @return Pinoco_PDOStatementWrapper + */ + public function query($sql) + { + return new Pinoco_PDOStatementWrapper( + $this->getConnection()->query($sql) + ); + } +} + +/** + * PDO Statement wrapper overrides PDO statement object. + * You can use also native functions. + * @package Pinoco + * @property-read string $queryString + * @method bool bindColumn() bindColumn( mixed $column , mixed &$param [, int $type [, int $maxlen [, mixed $driverdata ]]] ) + * @method bool bindParam() bindParam( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ]]] ) + * @method bool bindValue() bindValue( mixed $parameter , mixed $value [, int $data_type = PDO::PARAM_STR ] ) + * @method bool closeCursor() + * @method int columnCount() + * @method bool debugDumpParams() + * @method string errorCode() + * @method array errorInfo() + * @method string fetchColumn() fetchColumn([ int $column_number = 0 ] ) + * @method mixed fetchObject() fetchObject([ string $class_name = "stdClass" [, array $ctor_args ]] ) + * @method mixed getAttribute() getAttribute( int $attribute ) + * @method array getColumnMeta() getColumnMeta( int $column ) + * @method bool nextRowset() + * @method int rowCount() + * @method bool setAttribute() setAttribute( int $attribute , mixed $value ) + * @method bool setFetchMode() setFetchMode( int $mode ) + */ +class Pinoco_PDOStatementWrapper { + private $_stmt; + + /** + * @param PDOStatement $stmt + */ + public function __construct($stmt) + { + $this->_stmt = $stmt; + //$this->_stmt->setFetchMode(PDO::FETCH_CLASS, "Pinoco_Vars", array()); + } + + public function __call($name, $args) + { + return call_user_func_array(array($this->_stmt, $name), $args); + } + + /** + * Executes prepared query with parameters. + * No arguments: no-params. + * Single argument: + * array or array like: expanded as params. (both of map and seq) + * array incompatible: applied as sigle argument. + * Multiple arguments: applied to params as is. (only sequencial) + * + * @param mixed $args... + * @return int + */ + public function execute(/*[$args[, ...]]*/) + { + if(func_num_args() == 0) { + $args = array(); + } + else if(func_num_args() == 1) { + $a = func_get_arg(0); + if($a instanceof Pinoco_Vars || $a instanceof Pinoco_List) { + $args = $a->toArray(); + } + else if(is_array($a)) { + $args = $a; + } + else { + $args = array($a); + } + } + else { + $args = func_get_args(); + } + $this->_stmt->execute($args); + return $this->rowCount(); + } + + /** + * Alias to execute + * @param mixed $args... + * @return int + */ + public function exec(/*[$args[, ...]]*/) + { + $args = func_get_args(); + return call_user_func_array(array($this, 'execute'), $args); + } + + /** + * Calls execute and returns self + * @param mixed $args... + * @return Pinoco_PDOStatementWrapper + */ + public function query(/*[$args[, ...]]*/) + { + $args = func_get_args(); + call_user_func_array(array($this, 'execute'), $args); + return $this; + } + + /** + * Fetches the next row in result set. + * If FALSE returned, you should close cursor using closeCursor(). + * @return Pinoco_Vars + */ + public function fetch($orientation=PDO::FETCH_ORI_NEXT, $offset=0) + { + //return $this->_stmt->fetch(PDO::FETCH_CLASS, $orientation, $offset); + $r = $this->_stmt->fetch(PDO::FETCH_ASSOC, $orientation, $offset); + return $r !== FALSE ? Pinoco_Vars::fromArray($r) : $r; + } + + /** + * Fetches all results + * @return Pinoco_List + */ + public function fetchAll() + { + //return Pinoco::newList($this->_stmt->fetchAll(PDO::FETCH_CLASS)); + $rs = new Pinoco_List(); + $rows = $this->_stmt->fetchAll(PDO::FETCH_ASSOC); + foreach($rows as $r) { + $rs->push(Pinoco_Vars::fromArray($r)); + } + return $rs; + } + + /** + * Fetches single result + * @return Pinoco_Vars + */ + public function fetchOne() + { + $r = $this->fetch(); + try { $this->closeCursor(); } catch(PDOException $ex){ } + return $r; + } +} + diff --git a/_app/lib/Pinoco/Renderer.php b/_app/lib/Pinoco/Renderer.php index 8d43bf2..580b8ef 100644 --- a/_app/lib/Pinoco/Renderer.php +++ b/_app/lib/Pinoco/Renderer.php @@ -9,8 +9,8 @@ * @package Pinoco * @author Hisateru Tanaka * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License - * @version 0.3.0 - * @link http://code.google.com/p/pinoco/ + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco * @filesource */ diff --git a/_app/lib/Pinoco/VarsList.php b/_app/lib/Pinoco/VarsList.php index 4eb89f8..67dde1e 100644 --- a/_app/lib/Pinoco/VarsList.php +++ b/_app/lib/Pinoco/VarsList.php @@ -9,8 +9,8 @@ * @package Pinoco * @author Hisateru Tanaka * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License - * @version 0.3.0 - * @link http://code.google.com/p/pinoco/ + * @version 0.4.0 + * @link https://github.com/tanakahisateru/pinoco * @filesource */ @@ -54,9 +54,9 @@ public static function fromArray($src) public static function wrap(&$srcref) { $self = new Pinoco_Vars(); - $self->_vars = $srcref; + $self->_vars = &$srcref; return $self; - } + } /** * Returns a value or default by name. @@ -64,10 +64,14 @@ public static function wrap(&$srcref) * @param mixed $default * @return mixed */ - public function get($name) + public function get($name /*[, $default]*/) { if(array_key_exists($name, $this->_vars)) { - return $this->_vars[$name]; + $r = $this->_vars[$name]; + if($r instanceof Pinoco_LazyValueProxy) { + $r = $r->fetch($this); + } + return $r; } else { return func_num_args() > 1 ? func_get_arg(1) : $this->_default_val; @@ -110,6 +114,32 @@ public function set($name, $value) $this->_vars[$name] = $value; } + /** + * Sets a value to this object as given named dynamic value. + * The callback evaluted every time when fetched. + * @param string $name + * @param callable $callback + * @param array $context + * @return void + */ + public function registerAsDynamic($name, $callback, $context=array()) + { + $this->_vars[$name] = new Pinoco_LazyValueProxy($callback, false, $context); + } + + /** + * Sets a value to this object as given named lazy value. + * The callback evaluted as oneshot. + * @param string $name + * @param callable $callback + * @param array $context + * @return void + */ + public function registerAsLazy($name, $callback, $context=array()) + { + $this->_vars[$name] = new Pinoco_LazyValueProxy($callback, true, $context); + } + /** * Sets a default value for non existence property access. * @param mixed $value @@ -192,7 +222,29 @@ public function toArray($filter=false, $default=null, $modifier="%s") $arr = array(); $ks = $filter ? $filter : $this->keys(); foreach($ks as $k) { - $arr[sprintf($modifier, $k)] = $this->get($k, $default); + $name = (strpos($modifier, "%") !== FALSE) ? sprintf($modifier, $k) : ( + is_callable($modifier) ? call_user_func($modifier, $k) : ($modifier . $k) + ); + $arr[$name] = $this->get($k, $default); + } + return $arr; + } + + /** + * Exports properties to Array recursively. + * @param int $depth + * @return array + */ + public function toArrayRecurse($depth=false) + { + if($depth !== false && $depth == 0) { return $this; } + $arr = array(); + foreach($this->keys() as $k) { + $v = $this->get($k); + if($v instanceof Pinoco_Vars || $v instanceof Pinoco_List) { + $v = $v->toArrayRecurse($depth !== false ? $depth - 1 : false); + } + $arr[$k] = $v; } return $arr; } @@ -225,7 +277,7 @@ public function import($src, $filter=false, $default=null, $modifier="%s") } $ks = $filter ? $filter : array_keys($srcarr); foreach($ks as $k) { - $name = (strpos($modifier, "%") != -1) ? sprintf($modifier, $k) : ( + $name = (strpos($modifier, "%") !== FALSE) ? sprintf($modifier, $k) : ( is_callable($modifier) ? call_user_func($modifier, $k) : ($modifier . $k) ); $this->set($name, array_key_exists($k, $srcarr) ? $srcarr[$k] : $default); @@ -235,6 +287,68 @@ public function import($src, $filter=false, $default=null, $modifier="%s") public function __toString() { return __CLASS__; } // TODO: dump vars name/values } +/** + * Lazy value proxy + * @package Pinoco + * @internal + */ +class Pinoco_LazyValueProxy { + + private $callback; + private $context; + private $oneshot; + private $freeze; + private $value; + + /** + * Constructor to make an lazy value proxy. + * + * @param callable $callback + * @param boolean $oneshot + * @param array $context + */ + public function __construct($callback, $oneshot=false, $context=array()) + { + if(is_callable($callback)) { + $this->callback = $callback; + $this->oneshot = $oneshot; + $this->context = !empty($context) ? $context : array(); + $this->freeze = false; + $this->value = null; + } + else { + $this->freeze = true; + $this->value = $callback; + } + } + + /** + * Evalute real value. + * + * @param mixed $ovner + * @return mixed + */ + public function fetch($owner=null) + { + if($this->oneshot && $this->freeze) { + return $this->value; + } + $args = $this->context; + array_unshift($args, $owner); + $result = call_user_func_array($this->callback, $args); + if($result instanceof Pinoco_LazyValueProxy) { + $result = $result->fetch($owner); + } + if($this->oneshot) { + $this->freeze = true; + $this->value = $result; + } + return $result; + } + +} + + /** * List model * @package Pinoco @@ -273,7 +387,7 @@ public static function fromArray($src) public static function wrap(&$srcref) { $self = new Pinoco_List(); - $self->_arr = $srcref; + $self->_arr = &$srcref; return $self; } @@ -282,7 +396,7 @@ public static function wrap(&$srcref) * @param mixed $value,... * @return void */ - public function push($value) + public function push($value /*[, $value1[, ...]]*/) { $n = func_num_args(); for($i = 0; $i < $n; $i++) { @@ -305,7 +419,7 @@ public function pop() * @param mixed $value,... * @return void */ - public function unshift($value) + public function unshift($value /*[, $value1[, ...]]*/) { $n = func_num_args(); for($i = 0; $i < $n; $i++) { @@ -328,7 +442,7 @@ public function shift() * @param mixed $source * @return void */ - public function concat($source) + public function concat($source /*[, $source1[, ...]]*/) { $n = func_num_args(); for($i = 0; $i < $n; $i++) { @@ -388,7 +502,7 @@ public function reverse() * @param int $length * @return Pinoco_List */ - public function slice($offset) { // $length + public function slice($offset /*[, $length]*/) { if(func_num_args() >= 2) { $a1 = func_get_arg(1); return self::fromArray(array_slice($this->_arr, $offset, $a1)); @@ -405,7 +519,7 @@ public function slice($offset) { // $length * @param array $replacement * @return Pinoco_List; */ - public function splice($offset, $length=0) { // $replacement + public function splice($offset, $length /*[, $replacement]*/) { // $replacement if(func_num_args() >= 3) { $a2 = func_get_arg(2); return self::fromArray(array_splice($this->_arr, $offset, $length, $a2)); @@ -421,7 +535,7 @@ public function splice($offset, $length=0) { // $replacement * @param mixed $value * @return void */ - public function insert($offset, $value) + public function insert($offset, $value /*[, $value1[, ...]]*/) { $args = func_get_args(); array_shift($args); @@ -456,9 +570,9 @@ public function index($value) * @param mixed $default * @return unknown_type */ - public function get($idx) + public function get($idx /*[, $default]*/) { - if($idx < $this->count()) { + if(isset($this->_arr[$idx])) { return $this->_arr[$idx]; } else { @@ -473,10 +587,10 @@ public function get($idx) * @param mixed $default * @return void */ - public function set($idx, $value) + public function set($idx, $value /*[, $default]*/) { for($i = count($this->_arr); $i < $idx; $i++) { - $this->_arr[$idx] = func_num_args() > 2 ? func_get_arg(2) : $this->_default_val; //default?? + $this->_arr[$i] = func_num_args() > 2 ? func_get_arg(2) : $this->_default_val; //default?? } $this->_arr[$idx] = $value; } @@ -498,9 +612,37 @@ public function setDefault($value) */ public function toArray($modifier=null) { + $arr = array(); + if($modifier) { + foreach($this->_arr as $i=>$v) { + $name = (strpos($modifier, "%") !== FALSE) ? sprintf($modifier, $i) : ( + is_callable($modifier) ? call_user_func($modifier, $i) : ($modifier . $i) + ); + $arr[$name] = $v; + } + } + else { + foreach($this->_arr as $i=>$v) { + $arr[$i] = $v; + } + } + return $arr; + } + + /** + * Exports properties to Array recursively. + * @param int $depth + * @return array + */ + public function toArrayRecurse($depth=false) + { + if($depth !== false && $depth == 0) { return $this; } $arr = array(); foreach($this->_arr as $i=>$v) { - $arr[$modifier ? sprintf($modifier, $i) : $i] = $v; + if($v instanceof Pinoco_Vars || $v instanceof Pinoco_List) { + $v = $v->toArrayRecurse($depth !== false ? $depth - 1 : false); + } + $arr[$i] = $v; } return $arr; } @@ -598,6 +740,9 @@ public function valid() { return $this->_cur !== FALSE; } } /** + * Dynamic vars model base + * @package Pinoco + * @abstract */ class Pinoco_DynamicVars extends Pinoco_Vars { @@ -608,7 +753,7 @@ class Pinoco_DynamicVars extends Pinoco_Vars { * @return mixed * @see src/Pinoco/Pinoco_Vars#get($name) */ - public function get($name) + public function get($name /*[, $default]*/) { if(method_exists($this, 'get_' . $name)) { return call_user_func(array($this, 'get_' . $name)); @@ -682,20 +827,5 @@ public function getIterator() $arr = $this->toArray(); return new Pinoco_Iterator($arr); } - - public function _x_call($name, $args) // diabled temporaly - { - if(!$this->has($name)) { - return; - // throw new BadMethodCallException(); - } - $func = $this->get($name); - if(!is_callable($func)) { - return; - // throw new BadMethodCallException(); - } - - array_unshift($args, $this); - return call_user_func_array($func, $args); - } } +