diff --git a/.divinerconfig b/.divinerconfig index a04fd48..4805c55 100644 --- a/.divinerconfig +++ b/.divinerconfig @@ -1,20 +1,18 @@ { "name" : "libphutil", "src_base" : "https://github.com/facebook/libphutil/blob/master", "groups" : { "overview" : "Overview", "working" : "Working with libphutil", "util" : "Core Utilities", "library" : "Phutil Module System", "filesystem" : "Filesystem", "exec" : "Command Execution", "futures" : "Futures", - "storage" : "Storage", "markup" : "Markup", "console" : "Console Utilities", "xhpast" : "XHPAST (PHP/XHP Parser)", - "aphront" : "Aphront (Web Stack)", "conduit" : "Conduit (Service API)" } } diff --git a/.gitignore b/.gitignore index d7aa085..c3d9880 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ .DS_Store ._* -docs/ +/docs/ *.a *.o parser.yacc.cpp parser.yacc.hpp scanner.lex.cpp scanner.lex.hpp parser.yacc.output /support/xhpast/node_names.hpp /support/xhpast/xhpast /src/parser/xhpast/bin/xhpast diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 84a9f9b..594f938 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,157 +1,120 @@ array( - 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', - 'AphrontController' => 'aphront/controller', - 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', - 'AphrontDefaultApplicationController' => 'aphront/default/controller', - 'AphrontRequest' => 'aphront/request', - 'AphrontResponse' => 'aphront/response', - 'AphrontURIMapper' => 'aphront/mapper', - 'AphrontWebpageResponse' => 'aphront/response/webpage', 'CommandException' => 'future/exec', 'ConduitClient' => 'conduit/client', 'ConduitClientException' => 'conduit/client', 'ConduitFuture' => 'conduit/client', 'ExecFuture' => 'future/exec', 'FileFinder' => 'filesystem/filefinder', 'FileList' => 'filesystem/filelist', 'Filesystem' => 'filesystem', 'FilesystemException' => 'filesystem', 'Future' => 'future', 'FutureIterator' => 'future', 'HTTPFuture' => 'future/http', - 'LiskDAO' => 'lisk/dao', 'PhutilConsoleFormatter' => 'console', - 'PhutilDatabaseConnection' => 'storage/connection/base', 'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/default', 'PhutilInteractiveEditor' => 'console/editor', 'PhutilMarkupEngine' => 'markup/engine', 'PhutilMissingSymbolException' => 'symbols/exception/missing', - 'PhutilMySQLDatabaseConnection' => 'storage/connection/mysql', - 'PhutilQueryConnectionException' => 'storage/exception/connection', - 'PhutilQueryConnectionLostException' => 'storage/exception/connectionlost', - 'PhutilQueryCountException' => 'storage/exception/count', - 'PhutilQueryException' => 'storage/exception/base', - 'PhutilQueryObjectMissingException' => 'storage/exception/objectmissing', - 'PhutilQueryParameterException' => 'storage/exception/parameter', - 'PhutilQueryRecoverableException' => 'storage/exception/recoverable', 'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/blockstorage', 'PhutilRemarkupEngine' => 'markup/engine/remarkup', 'PhutilRemarkupEngineBlockRule' => 'markup/engine/remarkup/blockrule/base', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/remarkupcode', - 'PhutilRemarkupEngineRemarkupCounterExampleBlockRule' => 'markup/engine/remarkup/blockrule/remarkupcounterexample', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/remarkupdefault', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/remarkupheader', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/remarkupinline', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/remarkuplist', 'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/base', 'PhutilRemarkupRuleBold' => 'markup/engine/remarkup/markuprule/bold', 'PhutilRemarkupRuleEscapeHTML' => 'markup/engine/remarkup/markuprule/escapehtml', 'PhutilRemarkupRuleEscapeRemarkup' => 'markup/engine/remarkup/markuprule/escaperemarkup', 'PhutilRemarkupRuleHyperlink' => 'markup/engine/remarkup/markuprule/hyperlink', 'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/italics', 'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/linebreaks', 'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/monospace', 'PhutilSymbolLoader' => 'symbols', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/base', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/base', 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/xhpast', 'TempFile' => 'filesystem/tempfile', 'XHPASTNode' => 'parser/xhpast/api/node', 'XHPASTNodeList' => 'parser/xhpast/api/list', 'XHPASTSyntaxErrorException' => 'parser/xhpast/api/exception', 'XHPASTToken' => 'parser/xhpast/api/token', 'XHPASTTree' => 'parser/xhpast/api/tree', 'XHPASTTreeTestCase' => 'parser/xhpast/api/tree/__tests__', ), 'function' => array( 'Futures' => 'future', - '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf', - '_qsprintf_check_type' => 'xsprintf/qsprintf', 'array_select_keys' => 'utils', 'coalesce' => 'utils', 'csprintf' => 'xsprintf/csprintf', 'exec_manual' => 'future/exec', 'execx' => 'future/exec', 'id' => 'utils', 'idx' => 'utils', 'ipull' => 'utils', 'jsprintf' => 'xsprintf/jsprintf', 'mgroup' => 'utils', 'mpull' => 'utils', 'msort' => 'utils', 'newv' => 'utils', 'nonempty' => 'utils', 'phutil_autoload_class' => 'autoload', 'phutil_console_confirm' => 'console', 'phutil_console_format' => 'console', 'phutil_console_prompt' => 'console', 'phutil_console_wrap' => 'console', 'phutil_escape_html' => 'markup', 'phutil_get_library_name_for_root' => 'moduleutils', 'phutil_get_library_root' => 'moduleutils', 'phutil_get_library_root_for_path' => 'moduleutils', 'phutil_render_tag' => 'markup', - 'qsprintf' => 'xsprintf/qsprintf', - 'queryfx' => 'storage/queryfx', - 'queryfx_all' => 'storage/queryfx', - 'queryfx_one' => 'storage/queryfx', 'vcsprintf' => 'xsprintf/csprintf', 'vjsprintf' => 'xsprintf/jsprintf', - 'vqsprintf' => 'xsprintf/qsprintf', - 'vqueryfx' => 'storage/queryfx', 'xhp_parser_node_constants' => 'parser/xhpast/constants', 'xhpast_get_binary_path' => 'parser/xhpast/bin', 'xhpast_get_build_instructions' => 'parser/xhpast/bin', 'xhpast_get_parser_future' => 'parser/xhpast/bin', 'xhpast_is_available' => 'parser/xhpast/bin', 'xhpast_parser_token_constants' => 'parser/xhpast/constants', 'xsprintf' => 'xsprintf', + 'xsprintf_callback_example' => 'xsprintf', 'xsprintf_command' => 'xsprintf/csprintf', 'xsprintf_javascript' => 'xsprintf/jsprintf', - 'xsprintf_query' => 'xsprintf/qsprintf', ), 'requires_class' => array( - 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', - 'AphrontDefaultApplicationController' => 'AphrontController', 'ConduitFuture' => 'HTTPFuture', 'ExecFuture' => 'Future', 'HTTPFuture' => 'Future', 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', - 'PhutilMySQLDatabaseConnection' => 'PhutilDatabaseConnection', - 'PhutilQueryConnectionException' => 'PhutilQueryException', - 'PhutilQueryConnectionLostException' => 'PhutilQueryRecoverableException', - 'PhutilQueryCountException' => 'PhutilQueryException', - 'PhutilQueryObjectMissingException' => 'PhutilQueryException', - 'PhutilQueryParameterException' => 'PhutilQueryException', - 'PhutilQueryRecoverableException' => 'PhutilQueryException', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupCounterExampleBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupRuleBold' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeHTML' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeRemarkup' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleHyperlink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule', 'XHPASTTreeTestCase' => 'ArcanistPhutilTestCase', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php b/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php deleted file mode 100644 index 32f9bbe..0000000 --- a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php +++ /dev/null @@ -1,72 +0,0 @@ -request = $request; - return $this; - } - - final public function getRequest() { - return $this->request; - } - - final public function buildController() { - $map = $this->getURIMap(); - $mapper = new AphrontURIMapper($map); - $request = $this->getRequest(); - $path = $request->getPath(); - list($controller_class, $uri_data) = $mapper->mapPath($path); - - PhutilSymbolLoader::loadClass($controller_class); - $controller = newv($controller_class, array($request)); - - return array($controller, $uri_data); - } - - final public function setHost($host) { - $this->host = $host; - return $this; - } - - final public function getHost() { - return $this->host; - } - - final public function setPath($path) { - $this->path = $path; - return $this; - } - - final public function getPath() { - return $this->path; - } - -} diff --git a/src/aphront/applicationconfiguration/__init__.php b/src/aphront/applicationconfiguration/__init__.php deleted file mode 100644 index cb5f57c..0000000 --- a/src/aphront/applicationconfiguration/__init__.php +++ /dev/null @@ -1,14 +0,0 @@ -request = $request; - } - - final public function getRequest() { - return $this->request; - } - -} diff --git a/src/aphront/controller/__init__.php b/src/aphront/controller/__init__.php deleted file mode 100644 index aab2bf1..0000000 --- a/src/aphront/controller/__init__.php +++ /dev/null @@ -1,10 +0,0 @@ - 'AphrontDefaultApplicationController', - ); - } - - public function buildRequest() { - $request = new AphrontRequest($this->getHost(), $this->getPath()); - $request->setRequestData($_GET + $_POST); - return $request; - } - -} diff --git a/src/aphront/default/configuration/__init__.php b/src/aphront/default/configuration/__init__.php deleted file mode 100644 index 2c9eddd..0000000 --- a/src/aphront/default/configuration/__init__.php +++ /dev/null @@ -1,13 +0,0 @@ -getRequest(); - - $path = phutil_escape_html($request->getPath()); - $host = phutil_escape_html($request->getHost()); - $controller_name = phutil_escape_html(get_class($this)); - - $response = new AphrontWebpageResponse(); - $response->setContent(<< - - Aphront Default Application - - -

Welcome to Aphront

-

Things appear to be working properly.

-

Request Information

- - - - - - - - - - -
Host{$host}
Path{$path}
Controller{$controller_name}
- - - -EOPAGE - ); - - return $response; - } - -} diff --git a/src/aphront/default/controller/__init__.php b/src/aphront/default/controller/__init__.php deleted file mode 100644 index a2ed24a..0000000 --- a/src/aphront/default/controller/__init__.php +++ /dev/null @@ -1,14 +0,0 @@ -map = $map; - } - - final public function mapPath($path) { - $map = $this->map; - foreach ($map as $rule => $value) { - list($controller, $data) = $this->tryRule($rule, $value, $path); - if ($controller) { - foreach ($data as $k => $v) { - if (is_numeric($k)) { - unset($data[$k]); - } - } - return array($controller, $data); - } - } - - return array(null, null); - } - - final private function tryRule($rule, $value, $path) { - $match = null; - if (!preg_match('#^'.$rule.'#', $path, $match)) { - return array(null, null); - } - - if (!is_array($value)) { - return array($value, $match); - } - - $path = substr($path, strlen($match[0])); - foreach ($value as $srule => $sval) { - list($controller, $data) = $this->tryRule($srule, $sval, $path); - if ($controller) { - return array($controller, $data + $match); - } - } - - return array(null, null); - } -} diff --git a/src/aphront/mapper/__init__.php b/src/aphront/mapper/__init__.php deleted file mode 100644 index 46dee32..0000000 --- a/src/aphront/mapper/__init__.php +++ /dev/null @@ -1,10 +0,0 @@ -host = $host; - $this->path = $path; - } - - final public function setRequestData(array $request_data) { - $this->requestData = $request_data; - return $this; - } - - final public function getPath() { - return $this->path; - } - - final public function getHost() { - return $this->host; - } - - final public function getInt($name, $default = null) { - if (isset($this->requestData[$name])) { - return (int)$this->requestData[$name]; - } else { - return $default; - } - } - - final public function getStr($name, $default = null) { - if (isset($this->requestData[$name])) { - return (string)$this->requestData[$name]; - } else { - return $default; - } - } - - final public function getArr($name, $default = null) { - if (isset($this->requestData[$name]) && - is_array($this->requestData[$name])) { - return $this->requestData[$name]; - } else { - return $default; - } - } - - final public function getExists($name) { - return array_key_exists($name, $this->requestData); - } - - final public function isAjax() { - return $this->getExists(self::TYPE_AJAX); - } - -} diff --git a/src/aphront/request/__init__.php b/src/aphront/request/__init__.php deleted file mode 100644 index 3b8c8f9..0000000 --- a/src/aphront/request/__init__.php +++ /dev/null @@ -1,10 +0,0 @@ -content = $content; - return $this; - } - - public function buildResponseString() { - return $this->content; - } - -} diff --git a/src/aphront/response/webpage/__init__.php b/src/aphront/response/webpage/__init__.php deleted file mode 100644 index f65f1fa..0000000 --- a/src/aphront/response/webpage/__init__.php +++ /dev/null @@ -1,10 +0,0 @@ -getTransactionKey(); - if (!isset($levels[$key])) { - $levels[$key] = array( - 'read' => 0, - 'write' => 0, - ); - } - - return $levels[$key]; - } - - public function isReadLocking() { - $levels = &$this->getLockLevels(); - return ($levels['read'] > 0); - } - - public function isWriteLocking() { - $levels = &$this->getLockLevels(); - return ($levels['write'] > 0); - } - - public function startReadLocking() { - $levels = &$this->getLockLevels(); - ++$levels['read']; - return $this; - } - - public function startWriteLocking() { - $levels = &$this->getLockLevels(); - ++$levels['write']; - return $this; - } - - public function stopReadLocking() { - $levels = &$this->getLockLevels(); - if ($levels['read'] < 1) { - throw new Exception('Unable to stop read locking: not read locking.'); - } - --$levels['read']; - return $this; - } - - public function stopWriteLocking() { - $levels = &$this->getLockLevels(); - if ($levels['write'] < 1) { - throw new Exception('Unable to stop read locking: not write locking.'); - } - --$levels['write']; - return $this; - } - - protected function &getTransactionStack($key) { - if (!self::$transactionShutdownRegistered) { - self::$transactionShutdownRegistered = true; - register_shutdown_function( - array( - 'LiskConnection', - 'shutdownTransactionStacks', - )); - } - - if (!isset(self::$transactionStacks[$key])) { - self::$transactionStacks[$key] = array(); - } - - return self::$transactionStacks[$key]; - } - - public static function shutdownTransactionStacks() { - foreach (self::$transactionStacks as $stack) { - if ($stack === false) { - continue; - } - - $count = count($stack); - if ($count) { - throw new Exception( - 'Script exited with '.$count.' open transactions! The '. - 'transactions will be implicitly rolled back. Calls to '. - 'openTransaction() should always be paired with a call to '. - 'saveTransaction() or killTransaction(); you have an unpaired '. - 'call somewhere.', - $count); - } - } - } - - public function openTransaction() { - $key = $this->getTransactionKey(); - $stack = &$this->getTransactionStack($key); - - $new_transaction = !count($stack); - - // TODO: At least in development, push context information instead of - // `true' so we can report (or, at least, guess) where unpaired - // transaction calls happened. - $stack[] = true; - - end($stack); - $key = key($stack); - - if ($new_transaction) { - $this->query('START TRANSACTION'); - } else { - $this->query('SAVEPOINT '.$this->getSavepointName($key)); - } - } - - public function isInsideTransaction() { - $key = $this->getTransactionKey(); - $stack = &$this->getTransactionStack($key); - return (bool)count($stack); - } - - public function saveTransaction() { - $key = $this->getTransactionKey(); - $stack = &$this->getTransactionStack($key); - - if (!count($stack)) { - throw new Exception( - "No open transaction! Unable to save transaction, since there ". - "isn't one."); - } - - array_pop($stack); - - if (!count($stack)) { - $this->query('COMMIT'); - } - } - - public function saveTransactionUnless($cond) { - if ($cond) { - $this->killTransaction(); - } else { - $this->saveTransaction(); - } - } - - public function saveTransactionIf($cond) { - $this->saveTransactionUnless(!$cond); - } - - public function killTransaction() { - $key = $this->getTransactionKey(); - $stack = &$this->getTransactionStack($key); - - if (!count($stack)) { - throw new Exception( - "No open transaction! Unable to kill transaction, since there ". - "isn't one."); - } - - $count = count($stack); - - end($stack); - $key = key($stack); - array_pop($stack); - - if (!count($stack)) { - $this->query('ROLLBACK'); - } else { - $this->query( - 'ROLLBACK TO SAVEPOINT '.$this->getSavepointName($key) - ); - } - } - - protected function getSavepointName($key) { - return 'LiskSavepoint_'.$key; - } -} diff --git a/src/storage/connection/base/__init__.php b/src/storage/connection/base/__init__.php deleted file mode 100644 index db39502..0000000 --- a/src/storage/connection/base/__init__.php +++ /dev/null @@ -1,12 +0,0 @@ -configuration = $configuration; - } - - public function escapeString($string) { - if (!$this->connection) { - $this->establishConnection(); - } - return mysql_real_escape_string($string, $this->connection); - } - - public function escapeColumnName($name) { - return '`'.str_replace('`', '\\`', $name).'`'; - } - - public function escapeMultilineComment($comment) { - // These can either terminate a comment, confuse the hell out of the parser, - // make MySQL execute the comment as a query, or, in the case of semicolon, - // are quasi-dangerous because the semicolon could turn a broken query into - // a working query plus an ignored query. - - static $map = array( - '--' => '(DOUBLEDASH)', - '*/' => '(STARSLASH)', - '//' => '(SLASHSLASH)', - '#' => '(HASH)', - '!' => '(BANG)', - ';' => '(SEMICOLON)', - ); - - $comment = str_replace( - array_keys($map), - array_values($map), - $comment); - - // For good measure, kill anything else that isn't a nice printable - // character. - $comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment); - - return '/* '.$comment.' */'; - } - - public function escapeStringForLikeClause($value) { - $value = $this->escapeString($value); - // Ideally the query shouldn't be modified after safely escaping it, - // but we need to escape _ and % within LIKE terms. - $value = str_replace( - // Even though we've already escaped, we need to replace \ with \\ - // because MYSQL unescapes twice inside a LIKE clause. See note - // at mysql.com. However, if the \ is being used to escape a single - // quote ('), then the \ should not be escaped. Thus, after all \ - // are replaced with \\, we need to revert instances of \\' back to - // \'. - array('\\', '\\\\\'', '_', '%'), - array('\\\\', '\\\'', '\_', '\%'), - $value); - return $value; - } - - private function getConfiguration($key, $default = null) { - return idx($this->configuration, $key, $default); - } - - private function establishConnection() { - $this->connection = null; - - $conn = @mysql_connect( - $this->getConfiguration('host'), - $this->getConfiguration('user'), - $this->getConfiguration('pass'), - $new_link = true, - $flags = 0); - - if (!$conn) { - throw new PhutilQueryConnectionException(); - } - - $ret = @mysql_select_db($this->getConfiguration('database'), $conn); - if (!$ret) { - $this->throwQueryException($conn); - } - - $this->connection = $conn; - } - - public function getInsertID() { - return mysql_insert_id($this->requireConnection()); - } - - public function getAffectedRows() { - return mysql_affected_rows($this->requireConnection()); - } - - public function getTransactionKey() { - return (int)$this->requireConnection(); - } - - private function requireConnection() { - if (!$this->connection) { - throw new Exception("Connection is required."); - } - return $this->connection; - } - - public function selectAllResults() { - $result = array(); - $res = $this->lastResult; - if ($res == null) { - throw new Exception('No query result to fetch from!'); - } - while (($row = mysql_fetch_assoc($res)) !== false) { - $result[] = $row; - } - return $result; - } - - public function executeRawQuery($raw_query) { - $this->lastResult = null; - $retries = 3; - while ($retries--) { - try { - if (!$this->connection) { - $this->establishConnection(); - } - - $result = mysql_query($raw_query, $this->connection); - - if ($result) { - $this->lastResult = $result; - break; - } - - $this->throwQueryException($this->connection); - } catch (PhutilQueryConnectionLostException $ex) { - if (!$retries) { - throw $ex; - } - if ($this->isInsideTransaction()) { - throw $ex; - } - $this->connection = null; - } - } - } - - private function throwQueryException($connection) { - $errno = mysql_errno($connection); - $error = mysql_error($connection); - - switch ($errno) { - case 2013: // Connection Dropped - case 2006: // Gone Away - throw new PhutilQueryConnectionLostException("#{$errno}: {$error}"); - break; - case 1213: // Deadlock - case 1205: // Lock wait timeout exceeded - throw new PhutilQueryRecoverableException("#{$errno}: {$error}"); - break; - default: - // TODO: 1062 is syntax error, and quite terrible in production. - throw new PhutilQueryException("#{$errno}: {$error}"); - } - } - -} diff --git a/src/storage/connection/mysql/__init__.php b/src/storage/connection/mysql/__init__.php deleted file mode 100644 index 554c8d2..0000000 --- a/src/storage/connection/mysql/__init__.php +++ /dev/null @@ -1,17 +0,0 @@ -query = $query; - } - - public function getQuery() { - return $this->query; - } - -} diff --git a/src/storage/exception/parameter/__init__.php b/src/storage/exception/parameter/__init__.php deleted file mode 100644 index b1ced99..0000000 --- a/src/storage/exception/parameter/__init__.php +++ /dev/null @@ -1,12 +0,0 @@ -setName('Sawyer') - * ->setBreed('Pug') - * ->save(); - * - * Note that **Lisk automatically builds getters and setters for all of your - * object's properties** via __call(). You can override these by defining - * versions yourself. - * - * Calling save() will persist the object to the database. After calling - * save(), you can call getID() to retrieve the object's ID. - * - * To load objects by ID, use the load() method: - * - * $dog = id(new Dog())->load($id); - * - * This will load the Dog record with ID $id into $dog, or ##null## if no such - * record exists (load() is an instance method rather than a static method - * because PHP does not support late static binding, at least until PHP 5.3). - * - * To update an object, change its properties and save it: - * - * $dog->setBreed('Lab')->save(); - * - * To delete an object, call delete(): - * - * $dog->delete(); - * - * That's Lisk CRUD in a nutshell. - * - * = Queries = - * - * Often, you want to load a bunch of objects, or execute a more specialized - * query. Use loadAllWhere() or loadOneWhere() to do this: - * - * $pugs = $dog->loadAllWhere('breed = %s', 'Pug'); - * $sawyer = $dog->loadOneWhere('name = %s', 'Sawyer'); - * - * These methods work like @{function:queryfx}, but only take half of a query - * (the part after the WHERE keyword). Lisk will handle the connection, columns, - * and object construction; you are responsible for the rest of it. - * loadAllWhere() returns a list of objects, while loadOneWhere() returns a - * single object (or null). - * - * @task config Configuring Lisk - * @task load Loading Objects - * @task info Examining Objects - * @task save Writing Objects - * @task hook Hooks and Callbacks - * @task util Utilities - * - * @group storage - */ -abstract class LiskDAO { - - const CONFIG_OPTIMISTIC_LOCKS = 'enable-locks'; - const CONFIG_IDS = 'id-mechanism'; - const CONFIG_TIMESTAMPS = 'timestamps'; - const CONFIG_AUX_GUID = 'auxiliary-guid'; - const CONFIG_SERIALIZATION = 'col-serialization'; - - const SERIALIZATION_NONE = 'id'; - const SERIALIZATION_JSON = 'json'; - const SERIALIZATION_PHP = 'php'; - - const IDS_AUTOINCREMENT = 'ids-auto'; - const IDS_GUID = 'ids-guid'; - const IDS_MANUAL = 'ids-manual'; - - /** - * Build an empty object. - * - * @return obj Empty object. - */ - public function __construct() { - $id_key = $this->getIDKey(); - if ($id_key) { - $this->$id_key = null; - } - } - - abstract protected function establishConnection($mode); - - -/* -( Configuring Lisk )--------------------------------------------------- */ - - - /** - * Change Lisk behaviors, like optimistic locks and timestamps. If you want - * to change these behaviors, you should override this method in your child - * class and change the options you're interested in. For example: - * - * public function getConfiguration() { - * return array( - * Lisk_DataAccessObject::CONFIG_EXAMPLE => true, - * ) + parent::getConfiguration(); - * } - * - * The available options are: - * - * CONFIG_OPTIMISTIC_LOCKS - * Lisk automatically performs optimistic locking on objects, which protects - * you from read-modify-write concurrency problems. Lock failures are - * detected at write time and arise when two users read an object, then both - * save it. In theory, you should detect these failures and accommodate them - * in some sensible way (for instance, by showing the user differences - * between the original record and the copy they are trying to update, and - * prompting them to merge them). In practice, most Lisk tools are quick - * and dirty and don't get to that level of sophistication, but optimistic - * locks can still protect you from yourself sometimes. If you don't want - * to use optimistic locks, you can disable them. The performance cost of - * doing this locking is very very small (optimistic locks were chosen - * because they're simple and cheap, and highly optimized for the case where - * collisions are rare). By default, this option is OFF. - * - * CONFIG_IDS - * Lisk objects need to have a unique identifying ID. The three mechanisms - * available for generating this ID are IDS_AUTOINCREMENT (default, assumes - * the ID column is an autoincrement primary key), IDS_GUID (to generate a - * unique GUID for each object) or IDS_MANUAL (you are taking full - * responsibility for ID management). - * - * CONFIG_TIMESTAMPS - * Lisk can automatically handle keeping track of a `dateCreated' and - * `dateModified' column, which it will update when it creates or modifies - * an object. If you don't want to do this, you may disable this option. - * By default, this option is ON. - * - * CONFIG_AUX_GUID - * This option can be enabled by being set to some truthy value. The meaning - * of this value is defined by your guid generation mechanism. If this option - * is enabled, a `guid' property will be populated with a unique GUID when an - * object is created (or if it is saved and does not currently have one). You - * need to override generateGUID() and hook it into your GUID generation - * mechanism for this to work. By default, this option is OFF. - * - * CONFIG_SERIALIZATION - * You can optionally provide a column serialization map that will be applied - * to values when they are written to the database. For example: - * - * self::CONFIG_SERIALIZATION => array( - * 'complex' => self::SERIALIZATION_JSON, - * ) - * - * This will cause Lisk to JSON-serialize the 'complex' field before it is - * written, and unserialize it when it is read. - * - * - * @return dictionary Map of configuration options to values. - * - * @task config - */ - protected function getConfiguration() { - return array( - self::CONFIG_OPTIMISTIC_LOCKS => false, - self::CONFIG_IDS => self::IDS_AUTOINCREMENT, - self::CONFIG_TIMESTAMPS => true, - ); - } - - - /** - * Determine the setting of a configuration option for this class of objects. - * - * @param const Option name, one of the CONFIG_* constants. - * @return mixed Option value, if configured (null if unavailable). - * - * @task config - */ - public function getConfigOption($option_name) { - static $options = null; - - if (!isset($options)) { - $options = $this->getConfiguration(); - } - - return idx($options, $option_name); - } - - -/* -( Loading Objects )---------------------------------------------------- */ - - - /** - * Load an object by ID. You need to invoke this as an instance method, not - * a class method, because PHP doesn't have late static binding (until - * PHP 5.3.0). For example: - * - * $dog = id(new Dog())->load($dog_id); - * - * @param int Numeric ID identifying the object to load. - * @return obj|null Identified object, or null if it does not exist. - * - * @task load - */ - public function load($id) { - if (!($id = (int)$id)) { - throw new Exception("Bogus ID provided to load()."); - } - - return $this->loadOneWhere( - '%C = %d', - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $id); - } - - - /** - * Loads all of the objects, unconditionally. - * - * @return dict Dictionary of all persisted objects of this type, keyed - * on object ID. - * - * @task load - */ - public function loadAll() { - return $this->loadAllWhere('1 = 1'); - } - - - /** - * Load all objects which match a WHERE clause. You provide everything after - * the 'WHERE'; Lisk handles everything up to it. For example: - * - * $old_dogs = id(new Dog())->loadAllWhere('age > %d', 7); - * - * The pattern and arguments are as per queryfx(). - * - * @param string queryfx()-style SQL WHERE clause. - * @param ... Zero or more conversions. - * @return dict Dictionary of matching objects, keyed on ID. - * - * @task load - */ - public function loadAllWhere($pattern/*, $arg, $arg, $arg ... */) { - $args = func_get_args(); - $data = call_user_func_array( - array($this, 'loadRawDataWhere'), - $args); - return $this->loadAllFromArray($data); - } - - - /** - * Load a single object identified by a 'WHERE' clause. You provide - * everything after the 'WHERE', and Lisk builds the first half of the - * query. See loadAllWhere(). This method is similar, but returns a single - * result instead of a list. - * - * @param string queryfx()-style SQL WHERE clause. - * @param ... Zero or more conversions. - * @return obj|null Matching object, or null if no object matches. - * - * @task load - */ - public function loadOneWhere($pattern/*, $arg, $arg, $arg ... */) { - $args = func_get_args(); - $data = call_user_func_array( - array($this, 'loadRawDataWhere'), - $args); - - if (count($data) > 1) { - throw new PhutilQueryCountException( - "More than 1 result from loadOneWhere()!"); - } - - $data = reset($data); - if (!$data) { - return null; - } - - return $this->loadFromArray($data); - } - - - protected function loadRawDataWhere($pattern/*, $arg, $arg, $arg ... */) { - $connection = $this->getConnection('r'); - - $lock_clause = ''; - if ($connection->isReadLocking()) { - $lock_clause = 'FOR UPDATE'; - } else if ($connection->isWriteLocking()) { - $lock_clause = 'LOCK IN SHARE MODE'; - } - - $args = func_get_args(); - $args = array_slice($args, 1); - - $pattern = 'SELECT * FROM %T WHERE '.$pattern.' %Q'; - array_unshift($args, $this->getTableName()); - array_push($args, $lock_clause); - array_unshift($args, $pattern); - - return call_user_func_array( - array($connection, 'queryData'), - $args); - } - - - /** - * Reload an object from the database, discarding any changes to persistent - * properties. If the object uses optimistic locks and you are in a locking - * mode while transactional, this will effectively synchronize the locks. - * This is pretty heady. It is unlikely you need to use this method. - * - * @return this - * - * @task load - */ - public function reload() { - - if (!$this->getID()) { - throw new Exception("Unable to reload object that hasn't been loaded!"); - } - - $use_locks = $this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS); - - if (!$use_locks) { - $result = $this->loadOneWhere( - '%C = %d', - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $this->getID()); - } else { - $result = $this->loadOneWhere( - '%C = %d AND %C = %d', - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $this->getID(), - $this->columnNameFromPropertyName('version'), - $this->getVersion()); - } - - if (!$result) { - throw new PhutilQueryObjectMissingException($use_locks); - } - - return $this; - } - - - /** - * Initialize this object's properties from a dictionary. Generally, you - * load single objects with loadOneWhere(), but sometimes it may be more - * convenient to pull data from elsewhere directly (e.g., a complicated - * join via queryData()) and then load from an array representation. - * - * @param dict Dictionary of properties, which should be equivalent to - * selecting a row from the table or calling getProperties(). - * @return this - * - * @task load - */ - public function loadFromArray(array $row) { - $map = array(); - foreach ($row as $k => $v) { - $k = $this->propertyNameFromColumnName($k); - $map[$k] = $v; - } - - $this->willReadData($map); - - foreach ($map as $prop => $value) { - $this->$prop = $value; - } - - $this->didReadData(); - - return $this; - } - - - /** - * Initialize a list of objects from a list of dictionaries. Usually you - * load lists of objects with loadAllWhere(), but sometimes that isn't - * flexible enough. One case is if you need to do joins to select the right - * objects: - * - * function loadAllWithOwner($owner) { - * $data = $this->queryData( - * 'SELECT d.* - * FROM owner o - * JOIN owner_has_dog od ON o.id = od.ownerID - * JOIN dog d ON od.dogID = d.id - * WHERE o.id = %d', - * $owner); - * return $this->loadAllFromArray($data); - * } - * - * This is a lot messier than loadAllWhere(), but more flexible. - * - * @param list List of property dictionaries. - * @return dict List of constructed objects, keyed on ID. - * - * @task load - */ - public function loadAllFromArray(array $rows) { - $result = array(); - - $id_key = $this->getIDKey(); - - foreach ($rows as $row) { - $obj = clone $this; - if ($id_key) { - $result[$row[$this->columnNameFromPropertyName($id_key)]] - = $obj->loadFromArray($row); - } else { - $result[] = $obj->loadFromArray($row); - } - } - - return $result; - } - - -/* -( Examining Objects )-------------------------------------------------- */ - - - /** - * Retrieve the unique, numerical ID identifying this object. This value - * will be null if the object hasn't been persisted. - * - * @return int Unique numerical ID. - * - * @task info - */ - public function getID() { - $id_key = $this->getIDKeyForUse(); - return $this->$id_key; - } - - - /** - * Retrieve a list of all object properties. Note that some may be - * "transient", which means they should not be persisted to the database. - * Transient properties can be identified by calling - * getTransientProperties(). - * - * @return dict Dictionary of normalized (lowercase) to canonical (original - * case) property names. - * - * @task info - */ - protected function getProperties() { - static $properties = null; - if (!isset($properties)) { - $class = new ReflectionClass(get_class($this)); - $properties = array(); - foreach ($class->getProperties() as $p) { - $properties[strtolower($p->getName())] = $p->getName(); - } - - $id_key = $this->getIDKey(); - if ($id_key) { - if (!isset($properties[strtolower($id_key)])) { - $properties[strtolower($id_key)] = $id_key; - } - } - - if ($this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS)) { - $properties['version'] = 'version'; - } - - if ($this->getConfigOption(self::CONFIG_TIMESTAMPS)) { - $properties['datecreated'] = 'dateCreated'; - $properties['datemodified'] = 'dateModified'; - } - - if (!$this->isGUIDPrimaryID() && - $this->getConfigOption(self::CONFIG_AUX_GUID)) { - $properties['guid'] = 'guid'; - } - } - return $properties; - } - - - /** - * Check if a property exists on this object. - * - * @return string|null Canonical property name, or null if the property - * does not exist. - * - * @task info - */ - protected function checkProperty($property) { - static $properties = null; - if (!isset($properties)) { - $properties = $this->getProperties(); - } - - return idx($properties, strtolower($property)); - } - - - /** - * Get or build the database connection for this object. - * - * @return LiskDatabaseConnection Lisk connection object. - * - * @task info - */ - protected function getConnection($mode) { - if ($mode != 'r' && $mode != 'w') { - throw new Exception("Unknown mode '{$mode}', should be 'r' or 'w'."); - } - - // TODO: We don't do anything with the read/write mode right now, but - // should. - - if (!isset($this->__connection)) { - $this->__connection = $this->establishConnection($mode); - } - - return $this->__connection; - } - - - /** - * Convert this object into a property dictionary. This dictionary can be - * restored into an object by using loadFromArray() (unless you're using - * legacy features with CONFIG_CONVERT_CAMELCASE, but in that case you should - * just go ahead and die in a fire). - * - * @return dict Dictionary of object properties. - * - * @task info - */ - protected function getPropertyValues() { - $map = array(); - foreach ($this->getProperties() as $p) { - // We may receive a warning here for properties we've implicitly added - // through configuration; squelch it. - $map[$p] = @$this->$p; - } - return $map; - } - - - /** - * Convert this object into a property dictionary containing only properties - * which will be persisted to the database. - * - * @return dict Dictionary of persistent object properties. - * - * @task info - */ - protected function getPersistentPropertyValues() { - $map = $this->getPropertyValues(); - foreach ($this->getTransientProperties() as $p) { - unset($map[$p]); - } - return $map; - } - - -/* -( Writing Objects )---------------------------------------------------- */ - - - /** - * Persist this object to the database. In most cases, this is the only - * method you need to call to do writes. If the object has not yet been - * inserted this will do an insert; if it has, it will do an update. - * - * @return this - * - * @task save - */ - public function save() { - if ($this->shouldInsertWhenSaved()) { - return $this->insert(); - } else { - return $this->update(); - } - } - - - /** - * Save this object, forcing the query to use REPLACE regardless of object - * state. - * - * @return this - * - * @task save - */ - public function replace() { - return $this->insertRecordIntoDatabase('REPLACE'); - } - - - /** - * Save this object, forcing the query to use INSERT regardless of object - * state. - * - * @return this - * - * @task save - */ - public function insert() { - return $this->insertRecordIntoDatabase('INSERT'); - } - - - /** - * Save this object, forcing the query to use UPDATE regardless of object - * state. - * - * @return this - * - * @task save - */ - public function update() { - $use_locks = $this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS); - - $this->willSaveObject(); - $data = $this->getPersistentPropertyValues(); - $this->willWriteData($data); - - $map = array(); - foreach ($data as $k => $v) { - if ($use_locks && $k == 'version') { - continue; - } - $map[$this->columnNameFromPropertyName($k)] = $v; - } - - $conn = $this->getConnection('w'); - - if ($use_locks) { - $conn->query( - 'UPDATE %T SET %U, version = version + 1 WHERE %C = %d AND %C = %d', - $this->getTableName(), - $map, - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $this->getID(), - $this->columnNameFromPropertyName('version'), - $this->getVersion()); - } else { - $conn->query( - 'UPDATE %T SET %U WHERE %C = %d', - $this->getTableName(), - $map, - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $this->getID()); - } - - if ($conn->getAffectedRows() !== 1) { - throw new PhutilQueryObjectMissingException($use_locks); - } - - if ($use_locks) { - $this->setVersion($this->getVersion() + 1); - } - - $this->didWriteData(); - - return $this; - } - - - /** - * Delete this object, permanently. - * - * @return this - * - * @task save - */ - public function delete() { - $this->willDelete(); - - $conn = $this->getConnection('w'); - $conn->query( - 'DELETE FROM %T WHERE %C = %d', - $this->getTableName(), - $this->columnNameFromPropertyName($this->getIDKeyForUse()), - $this->getID()); - - $this->didDelete(); - - return $this; - } - - - /** - * Internal implementation of INSERT and REPLACE. - * - * @param const Either "INSERT" or "REPLACE", to force the desired mode. - * - * @task save - */ - protected function insertRecordIntoDatabase($mode) { - $this->willSaveObject(); - $data = $this->getPersistentPropertyValues(); - - $id_mechanism = $this->getConfigOption(self::CONFIG_IDS); - switch ($id_mechanism) { - // If we are using autoincrement IDs, let MySQL assign the value for the - // ID column. - case self::IDS_AUTOINCREMENT: - unset($data[$this->getIDKeyForUse()]); - break; - case self::IDS_GUID: - if (empty($data[$this->getIDKeyForUse()])) { - $guid = $this->generateGUID(); - $this->setID($guid); - $data[$this->getIDKeyForUse()] = $guid; - } - break; - case self::IDS_MANUAL: - break; - default: - throw new Exception('Unknown CONFIG_IDs mechanism!'); - } - - if ($this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS)) { - $data['version'] = 0; - } - - $this->willWriteData($data); - - $columns = array_keys($data); - foreach ($columns as $k => $property) { - $columns[$k] = $this->columnNameFromPropertyName($property); - } - - $conn = $this->getConnection('w'); - - $conn->query( - '%Q INTO %T (%LC) VALUES (%Ls)', - $mode, - $this->getTableName(), - $columns, - $data); - - // Update the object with the initial Version value - if ($this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS)) { - $this->setVersion(0); - } - - // Only use the insert id if this table is using auto-increment ids - if ($id_mechanism === self::IDS_AUTOINCREMENT) { - $this->setID($conn->getInsertID()); - } - - $this->didWriteData(); - - return $this; - } - - - /** - * Method used to determine whether to insert or update when saving. - * - * @return bool true if the record should be inserted - */ - protected function shouldInsertWhenSaved() { - $key_type = $this->getConfigOption(self::CONFIG_IDS); - $use_locks = $this->getConfigOption(self::CONFIG_OPTIMISTIC_LOCKS); - - if ($key_type == self::IDS_MANUAL) { - if ($use_locks) { - // If we are manually keyed and the object has a version (which means - // that it has been saved to the DB before), do an update, otherwise - // perform an insert. - if ($this->getID() && $this->getVersion() !== null) { - return false; - } else { - return true; - } - } else { - throw new Exception( - 'You are not using optimistic locks, but are using manual IDs. You '. - 'must override the shouldInsertWhenSaved() method to properly '. - 'detect when to insert a new record.'); - } - } else { - return !$this->getID(); - } - } - - -/* -( Hooks and Callbacks )------------------------------------------------ */ - - - /** - * Retrieve the database table name. By default, this is the class name. - * - * @return string Table name for object storage. - * - * @task hook - */ - public function getTableName() { - return get_class($this); - } - - - /** - * Helper: Whether this class is configured to use GUIDs as the primary ID. - * @task internal - */ - private function isGUIDPrimaryID() { - return ($this->getConfigOption(self::CONFIG_IDS) === self::IDS_GUID); - } - - - /** - * Retrieve the primary key column, "id" by default. If you can not - * reasonably name your ID column "id", override this method. - * - * @return string Name of the ID column. - * - * @task hook - */ - public function getIDKey() { - return - $this->isGUIDPrimaryID() ? - 'guid' : - 'id'; - } - - - protected function getIDKeyForUse() { - $id_key = $this->getIDKey(); - if (!$id_key) { - throw new Exception( - "This DAO does not have a single-part primary key. The method you ". - "called requires a single-part primary key."); - } - return $id_key; - } - - - /** - * Generate a new GUID, used by CONFIG_AUX_GUID and IDS_GUID. - * - * @return guid Unique, newly allocated GUID. - * - * @task hook - */ - protected function generateGUID() { - throw new Exception( - "To use CONFIG_AUX_GUID or IDS_GUID, you need to overload ". - "generateGUID() to perform GUID generation."); - } - - - /** - * If your object has properties which you don't want to be persisted to the - * database, you can override this method and specify them. - * - * @return list List of properties which should NOT be persisted. - * Property names should be in normalized (lowercase) form. - * By default, all properties are persistent. - * - * @task hook - */ - protected function getTransientProperties() { - return array(); - } - - - /** - * Hook to apply serialization or validation to data before it is written to - * the database. See also willReadData(). - * - * @task hook - */ - protected function willWriteData(array &$data) { - $this->applyLiskDataSerialization($data, false); - } - - - /** - * Hook to perform actions after data has been written to the database. - * - * @task hook - */ - protected function didWriteData() {} - - - /** - * Hook to make internal object state changes prior to INSERT, REPLACE or - * UPDATE. - * - * @task hook - */ - protected function willSaveObject() { - $use_timestamps = $this->getConfigOption(self::CONFIG_TIMESTAMPS); - - if ($use_timestamps) { - if (!$this->getDateCreated()) { - $this->setDateCreated(time()); - } - $this->setDateModified(time()); - } - - if (($this->isGUIDPrimaryID() && !$this->getID())) { - // If GUIDs are the primary ID, the subclass could have overridden the - // name of the ID column. - $this->setID($this->generateGUID()); - } else if ($this->getConfigOption(self::CONFIG_AUX_GUID) && - !$this->getGUID()) { - // The subclass could still want GUIDs. - $this->setGUID($this->generateGUID()); - } - } - - - /** - * Hook to apply serialization or validation to data as it is read from the - * database. See also willWriteData(). - * - * @task hook - */ - protected function willReadData(array &$data) { - $this->applyLiskDataSerialization($data, $deserialize = true); - } - - /** - * Hook to perform an action on data after it is read from the database. - * - * @task hook - */ - protected function didReadData() {} - - /** - * Hook to perform an action before the deletion of an object. - * - * @task hook - */ - protected function willDelete() {} - - /** - * Hook to perform an action after the deletion of an object. - * - * @task hook - */ - protected function didDelete() {} - -/* -( Utilities )---------------------------------------------------------- */ - - - /** - * Applies configured serialization to a dictionary of values. - * - * @task util - */ - protected function applyLiskDataSerialization(array &$data, $deserialize) { - $serialization = $this->getConfigOption(self::CONFIG_SERIALIZATION); - if ($serialization) { - foreach (array_intersect_key($serialization, $data) as $col => $format) { - switch ($format) { - case self::SERIALIZATION_NONE: - break; - case self::SERIALIZATION_PHP: - if ($deserialize) { - $data[$col] = unserialize($data[$col]); - } else { - $data[$col] = serialize($data[$col]); - } - break; - case self::SERIALIZATION_JSON: - if ($deserialize) { - $data[$col] = json_decode($data[$col], true); - } else { - $data[$col] = json_encode($data[$col]); - } - break; - default: - throw new Exception("Unknown serialization format '{$format}'."); - } - } - } - } - - - /** - * Black magic. Builds implied get*() and set*() for all properties. - * - * @param string Method name. - * @param list Argument vector. - * @return mixed get*() methods return the property value. set*() methods - * return $this. - * @task util - */ - public function __call($method, $args) { - if (!strncmp($method, 'get', 3)) { - $property = substr($method, 3); - if (!($property = $this->checkProperty($property))) { - throw new Exception("Bad getter call: {$method}"); - } - if (count($args) !== 0) { - throw new Exception("Getter call should have zero args: {$method}"); - } - return @$this->$property; - } - - if (!strncmp($method, 'set', 3)) { - $property = substr($method, 3); - $property = $this->checkProperty($property); - if (!$property) { - throw new Exception("Bad setter call: {$method}"); - } - if (count($args) !== 1) { - throw new Exception("Setter should have exactly one arg: {$method}"); - } - if ($property == 'ID') { - $property = $this->getIDKeyForUse(); - } - $this->$property = $args[0]; - return $this; - } - - throw new Exception("Unable to resolve method: {$method}."); - } -} diff --git a/src/storage/lisk/dao/__init__.php b/src/storage/lisk/dao/__init__.php deleted file mode 100644 index 4d73a63..0000000 --- a/src/storage/lisk/dao/__init__.php +++ /dev/null @@ -1,14 +0,0 @@ -executeRawQuery($query); -} - -/** - * @group storage - */ -function vqueryfx($conn, $sql, $argv) { - array_unshift($argv, $conn, $sql); - return call_user_func_array('queryfx', $argv); -} - -/** - * @group storage - */ -function queryfx_all($conn, $sql/*, ... */) { - $argv = func_get_args(); - $ret = call_user_func_array('queryfx', $argv); - return $conn->selectAllResults($ret); -} - -/** - * @group storage - */ -function queryfx_one($conn, $sql/*, ... */) { - $argv = func_get_args(); - $ret = call_user_func_array('queryfx_all', $argv); - if (count($ret) > 1) { - throw new PhutilQueryCountException( - 'Query returned more than one row.'); - } else if (count($ret)) { - return reset($ret); - } - return null; -} diff --git a/src/symbols/exception/missing/PhutilMissingSymbolException.php b/src/symbols/exception/missing/PhutilMissingSymbolException.php index d78cfb9..e14ca68 100644 --- a/src/symbols/exception/missing/PhutilMissingSymbolException.php +++ b/src/symbols/exception/missing/PhutilMissingSymbolException.php @@ -1,27 +1,28 @@ and %<. - * - * %> ("Prefix") - * Escapes a prefix query for a LIKE clause. For example: - * - * // Find all rows where `name` starts with $prefix. - * qsprintf($conn, 'WHERE name LIKE %>', $prefix); - * - * %< ("Suffix") - * Escapes a suffix query for a LIKE clause. For example: - * - * // Find all rows where `name` ends with $suffix. - * qsprintf($conn, 'WHERE name LIKE %<', $suffix); - * - * @group storage - */ -function qsprintf($conn, $pattern/*, ... */) { - $args = func_get_args(); - array_shift($args); - return xsprintf('xsprintf_query', $conn, $args); -} - -/** - * @group storage - */ -function vqsprintf($conn, $pattern, array $argv) { - array_unshift($argv, $pattern); - return xsprintf('xsprintf_query', $conn, $argv); -} - - -/** - * xsprintf() callback for encoding SQL queries. See qsprintf(). - * @group storage - */ -function xsprintf_query($userdata, &$pattern, &$pos, &$value, &$length) { - $type = $pattern[$pos]; - $conn = $userdata; - $next = (strlen($pattern) > $pos + 1) ? $pattern[$pos + 1] : null; - - $nullable = false; - $done = false; - - $prefix = ''; - - switch ($type) { - case '=': // Nullable test - switch ($next) { - case 'd': - case 'f': - case 's': - $pattern = substr_replace($pattern, '', $pos, 1); - $length = strlen($pattern); - $type = 's'; - if ($value === null) { - $value = 'IS NULL'; - $done = true; - } else { - $prefix = '= '; - $type = $next; - } - break; - default: - throw new Exception('Unknown conversion, try %=d, %=s, or %=f.'); - } - break; - - case 'n': // Nullable... - switch ($next) { - case 'd': // ...integer. - case 'f': // ...float. - case 's': // ...string. - $pattern = substr_replace($pattern, '', $pos, 1); - $length = strlen($pattern); - $type = $next; - $nullable = true; - break; - default: - throw new Exception('Unknown conversion, try %nd or %ns.'); - } - break; - - case 'L': // List of.. - _qsprintf_check_type($value, "L{$next}", $pattern); - $pattern = substr_replace($pattern, '', $pos, 1); - $length = strlen($pattern); - $type = 's'; - $done = true; - - switch ($next) { - case 'd': // ...integers. - $value = implode(', ', array_map('intval', $value)); - break; - case 's': // ...strings. - foreach ($value as $k => $v) { - $value[$k] = $conn->escapeString($v); - } - $value = implode(', ', $value); - break; - case 'C': // ...columns. - foreach ($value as $k => $v) { - $value[$k] = $conn->escapeColumnName($v); - } - $value = implode(', ', $value); - break; - default: - throw new Exception("Unknown conversion %L{$next}."); - } - break; - } - - if (!$done) { - _qsprintf_check_type($value, $type, $pattern); - switch ($type) { - case 's': // String - if ($nullable && $value === null) { - $value = 'NULL'; - } else { - $value = "'".$conn->escapeString($value)."'"; - } - $type = 's'; - break; - - case 'Q': // Query Fragment - $type = 's'; - break; - - case '~': // Like Substring - case '>': // Like Prefix - case '<': // Like Suffix - $value = $conn->escapeStringForLikeClause($value); - switch ($type) { - case '~': $value = "'%".$value."%'"; break; - case '>': $value = "'" .$value."%'"; break; - case '<': $value = "'%".$value. "'"; break; - } - $type = 's'; - break; - - case 'f': // Float - if ($nullable && $value === null) { - $value = 'NULL'; - } else { - $value = (float)$value; - } - $type = 's'; - break; - - case 'd': // Integer - if ($nullable && $value === null) { - $value = 'NULL'; - } else { - $value = (int)$value; - } - $type = 's'; - break; - - case 'T': // Table - case 'C': // Column - $value = $conn->escapeColumnName($value); - $type = 's'; - break; - - case 'K': // Komment - $value = $conn->escapeMultilineComment($value); - $type = 's'; - break; - - default: - throw new Exception("Unknown conversion '%{$type}'."); - - } - } - - if ($prefix) { - $value = $prefix.$value; - } - $pattern[$pos] = $type; -} - - -/** - * @group storage - */ -function _qsprintf_check_type($value, $type, $query) { - switch ($type) { - case 'Ld': case 'Ls': case 'LC': case 'LA': case 'LO': - if (!is_array($value)) { - throw new PhutilQueryParameterException( - $query, - "Expected array argument for %{$type} conversion."); - } - if (empty($value)) { - throw new PhutilQueryParameterException( - $query, - "Array for %{$type} conversion is empty."); - } - - foreach ($value as $scalar) { - _qsprintf_check_scalar_type($scalar, $type, $query); - } - break; - default: - _qsprintf_check_scalar_type($value, $type, $query); - } -} - - -/** - * @group storage - */ -function _qsprintf_check_scalar_type($value, $type, $query) { - switch ($type) { - case 'Q': case 'LC': case 'T': case 'C': - if (!is_string($value)) { - throw new PhutilQueryParameterException( - $query, - "Expected a string for %{$type} conversion."); - } - break; - - case 'Ld': case 'd': case 'f': - if (!is_null($value) && !is_scalar($value)) { - throw new PhutilQueryParameterException( - $query, - "Expected a scalar or null for %{$type} conversion."); - } - break; - - case 'Ls': case 's': - case '~': case '>': case '<': case 'K': - if (!is_null($value) && !is_scalar($value)) { - throw new PhutilQueryParameterException( - $query, - "Expected a scalar or null for %{$type} conversion."); - } - break; - - case 'LA': case 'LO': - if (!is_null($value) && !is_scalar($value) && - !(is_array($value) && !empty($value))) { - throw new PhutilQueryParameterException( - $query, - "Expected a scalar or null or non-empty array for ". - "%{$type} conversion."); - } - break; - default: - throw new Exception("Unknown conversion '{$type}'."); - } -} diff --git a/webroot/index.php b/webroot/index.php deleted file mode 100644 index c907d21..0000000 --- a/webroot/index.php +++ /dev/null @@ -1,60 +0,0 @@ -setHost($host); -$application->setPath($path); -$request = $application->buildRequest(); -$application->setRequest($request); -list($controller, $uri_data) = $application->buildController(); -$controller->willProcessRequest($uri_data); -$response = $controller->processRequest(); - -echo $response->buildResponseString(); - -/** - * @group aphront - */ -function setup_aphront_basics() { - @include_once 'libphutil/src/__phutil_library_init__.php'; - if (!@constant('__LIBPHUTIL__')) { - echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ". - "include the parent directory of libphutil/.\n"; - exit(1); - } - - if (!ini_get('date.timezone')) { - date_default_timezone_set('America/Los_Angeles'); - } -}