/core/xpdo/xpdo.class.php
PHP | 3163 lines | 2334 code | 88 blank | 741 comment | 514 complexity | 569a87e6f11dd2b7effd7bf4b3208e25 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- <?php
- /*
- * OpenExpedio ("xPDO") is an ultra-light, PHP 5.2+ compatible ORB (Object-
- * Relational Bridge) library based around PDO (http://php.net/pdo/).
- *
- * Copyright 2010-2015 by MODX, LLC.
- *
- * This file is part of xPDO.
- *
- * xPDO is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2 of the License, or (at your option) any later
- * version.
- *
- * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
- * Suite 330, Boston, MA 02111-1307 USA
- */
- /**
- * This is the main file to include in your scripts to use xPDO.
- *
- * @author Jason Coward <xpdo@opengeek.com>
- * @copyright Copyright (C) 2006-2014, Jason Coward
- * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License v2
- * @package xpdo
- */
- if (!defined('XPDO_PHP_VERSION')) {
- /**
- * Defines the PHP version string xPDO is running under.
- */
- define('XPDO_PHP_VERSION', phpversion());
- }
- if (!defined('XPDO_CLI_MODE')) {
- if (php_sapi_name() == 'cli') {
- /**
- * This constant defines if xPDO is operating from the CLI.
- */
- define('XPDO_CLI_MODE', true);
- } else {
- /** @ignore */
- define('XPDO_CLI_MODE', false);
- }
- }
- if (!defined('XPDO_CORE_PATH')) {
- /**
- * @internal This global variable is only used to set the {@link
- * XPDO_CORE_PATH} value upon initial include of this file. Not meant for
- * external use.
- * @var string
- * @access private
- */
- $xpdo_core_path= strtr(realpath(dirname(__FILE__)), '\\', '/') . '/';
- /**
- * @var string The full path to the xPDO root directory.
- *
- * Use of this constant is recommended for use when building any path in
- * your xPDO code.
- *
- * @access public
- */
- define('XPDO_CORE_PATH', $xpdo_core_path);
- }
- if (!class_exists('PDO')) {
- //@todo Handle PDO configuration errors here.
- }
- /**
- * A wrapper for PDO that powers an object-relational data model.
- *
- * xPDO provides centralized data access via a simple object-oriented API, to
- * a defined data structure. It provides the de facto methods for connecting
- * to a data source, getting persistence metadata for any class extended from
- * the {@link xPDOObject} class (core or custom), loading data source managers
- * when needed to manage table structures, and retrieving instances (or rows) of
- * any object in the model.
- *
- * Through various extensions, you can also reverse and forward engineer classes
- * and metadata maps for xPDO, have classes, models, and properties maintain
- * their own containers (databases, tables, columns, etc.) or changes to them,
- * and much more.
- *
- * @package xpdo
- */
- class xPDO {
- /**#@+
- * Constants
- */
- const OPT_AUTO_CREATE_TABLES = 'auto_create_tables';
- const OPT_BASE_CLASSES = 'base_classes';
- const OPT_BASE_PACKAGES = 'base_packages';
- const OPT_CACHE_COMPRESS = 'cache_compress';
- const OPT_CACHE_DB = 'cache_db';
- const OPT_CACHE_DB_COLLECTIONS = 'cache_db_collections';
- const OPT_CACHE_DB_OBJECTS_BY_PK = 'cache_db_objects_by_pk';
- const OPT_CACHE_DB_EXPIRES = 'cache_db_expires';
- const OPT_CACHE_DB_HANDLER = 'cache_db_handler';
- const OPT_CACHE_DB_SIG_CLASS = 'cache_db_sig_class';
- const OPT_CACHE_DB_SIG_GRAPH = 'cache_db_sig_graph';
- const OPT_CACHE_EXPIRES = 'cache_expires';
- const OPT_CACHE_FORMAT = 'cache_format';
- const OPT_CACHE_HANDLER = 'cache_handler';
- const OPT_CACHE_KEY = 'cache_key';
- const OPT_CACHE_PATH = 'cache_path';
- const OPT_CACHE_PREFIX = 'cache_prefix';
- const OPT_CACHE_ATTEMPTS = 'cache_attempts';
- const OPT_CACHE_ATTEMPT_DELAY = 'cache_attempt_delay';
- const OPT_CALLBACK_ON_REMOVE = 'callback_on_remove';
- const OPT_CALLBACK_ON_SAVE = 'callback_on_save';
- const OPT_CONNECTIONS = 'connections';
- const OPT_CONN_INIT = 'connection_init';
- const OPT_CONN_MUTABLE = 'connection_mutable';
- const OPT_OVERRIDE_TABLE_TYPE = 'override_table';
- const OPT_HYDRATE_FIELDS = 'hydrate_fields';
- const OPT_HYDRATE_ADHOC_FIELDS = 'hydrate_adhoc_fields';
- const OPT_HYDRATE_RELATED_OBJECTS = 'hydrate_related_objects';
- const OPT_LOCKFILE_EXTENSION = 'lockfile_extension';
- const OPT_USE_FLOCK = 'use_flock';
- /**
- * @deprecated
- * @see call()
- */
- const OPT_LOADER_CLASSES = 'loader_classes';
- const OPT_ON_SET_STRIPSLASHES = 'on_set_stripslashes';
- const OPT_SETUP = 'setup';
- const OPT_TABLE_PREFIX = 'table_prefix';
- const OPT_VALIDATE_ON_SAVE = 'validate_on_save';
- const OPT_VALIDATOR_CLASS = 'validator_class';
- const LOG_LEVEL_FATAL = 0;
- const LOG_LEVEL_ERROR = 1;
- const LOG_LEVEL_WARN = 2;
- const LOG_LEVEL_INFO = 3;
- const LOG_LEVEL_DEBUG = 4;
- const SCHEMA_VERSION = '1.1';
- /**
- * @var PDO A reference to the PDO instance used by the current xPDOConnection.
- */
- public $pdo= null;
- /**
- * @var array Configuration options for the xPDO instance.
- */
- public $config= null;
- /**
- * @var xPDODriver An xPDODriver instance for the xPDOConnection instances to use.
- */
- public $driver= null;
- /**
- * A map of data source meta data for all loaded classes.
- * @var array
- * @access public
- */
- public $map= array ();
- /**
- * A default package for specifying classes by name.
- * @var string
- * @access public
- */
- public $package= '';
- /**
- * An array storing packages and package-specific information.
- * @var array
- * @access public
- */
- public $packages= array ();
- /**
- * {@link xPDOManager} instance, loaded only if needed to manage datasource
- * containers, data structures, etc.
- * @var xPDOManager
- * @access public
- */
- public $manager= null;
- /**
- * @var xPDOCacheManager The cache service provider registered for this xPDO
- * instance.
- */
- public $cacheManager= null;
- /**
- * @var string A root path for file-based caching services to use.
- */
- private $cachePath= null;
- /**
- * @var array An array of supplemental service classes for this xPDO instance.
- */
- public $services= array ();
- /**
- * @var float Start time of the request, initialized when the constructor is
- * called.
- */
- public $startTime= 0;
- /**
- * @var int The number of direct DB queries executed during a request.
- */
- public $executedQueries= 0;
- /**
- * @var int The amount of request handling time spent with DB queries.
- */
- public $queryTime= 0;
- public $classMap = array();
- /**
- * @var xPDOConnection The current xPDOConnection for this xPDO instance.
- */
- public $connection = null;
- /**
- * @var array PDO connections managed by this xPDO instance.
- */
- private $_connections = array();
- /**
- * @var integer The logging level for the xPDO instance.
- */
- protected $logLevel= xPDO::LOG_LEVEL_FATAL;
- /**
- * @var string The default logging target for the xPDO instance.
- */
- protected $logTarget= 'ECHO';
- /**
- * Indicates the debug state of this instance.
- * @var boolean Default is false.
- * @access protected
- */
- protected $_debug= false;
- /**
- * A global cache flag that can be used to enable/disable all xPDO caching.
- * @var boolean All caching is disabled by default.
- * @access public
- */
- public $_cacheEnabled= false;
- /**
- * Indicates the opening escape character used for a particular database engine.
- * @var string
- * @access public
- */
- public $_escapeCharOpen= '';
- /**
- * Indicates the closing escape character used for a particular database engine.
- * @var string
- * @access public
- */
- public $_escapeCharClose= '';
- /**
- * Represents the character used for quoting strings for a particular driver.
- * @var string
- */
- public $_quoteChar= "'";
- /**
- * @var array A static collection of xPDO instances.
- */
- protected static $instances = array();
- /**
- * Create, retrieve, or update specific xPDO instances.
- *
- * @static
- * @param string|int|null $id An optional identifier for the instance. If not set
- * a uniqid will be generated and used as the key for the instance.
- * @param array|null $config An optional array of config data for the instance.
- * @param bool $forceNew If true a new instance will be created even if an instance
- * with the provided $id already exists in xPDO::$instances.
- * @throws xPDOException If a valid instance is not retrieved.
- * @return xPDO An instance of xPDO.
- */
- public static function getInstance($id = null, $config = null, $forceNew = false) {
- $instances =& self::$instances;
- if (is_null($id)) {
- if (!is_null($config) || $forceNew || empty($instances)) {
- $id = uniqid(__CLASS__);
- } else {
- $id = key($instances);
- }
- }
- if ($forceNew || !array_key_exists($id, $instances) || !($instances[$id] instanceof xPDO)) {
- $instances[$id] = new xPDO(null, null, null, $config);
- } elseif ($instances[$id] instanceof xPDO && is_array($config)) {
- $instances[$id]->config = array_merge($instances[$id]->config, $config);
- }
- if (!($instances[$id] instanceof xPDO)) {
- throw new xPDOException("Error getting " . __CLASS__ . " instance, id = {$id}");
- }
- return $instances[$id];
- }
- /**
- * The xPDO Constructor.
- *
- * This method is used to create a new xPDO object with a connection to a
- * specific database container.
- *
- * @param mixed $dsn A valid DSN connection string.
- * @param string $username The database username with proper permissions.
- * @param string $password The password for the database user.
- * @param array|string $options An array of xPDO options. For compatibility with previous
- * releases, this can also be a single string representing a prefix to be applied to all
- * database container (i. e. table) names, to isolate multiple installations or conflicting
- * table names that might need to coexist in a single database container. It is preferrable to
- * include the table_prefix option in the array for future compatibility.
- * @param array|null $driverOptions Driver-specific PDO options.
- * @throws xPDOException If an error occurs creating the instance.
- * @return xPDO A unique xPDO instance.
- */
- public function __construct($dsn, $username= '', $password= '', $options= array(), $driverOptions= null) {
- try {
- $this->config = $this->initConfig($options);
- $this->setLogLevel($this->getOption('log_level', null, xPDO::LOG_LEVEL_FATAL, true));
- $this->setLogTarget($this->getOption('log_target', null, XPDO_CLI_MODE ? 'ECHO' : 'HTML', true));
- if (!empty($dsn)) {
- $this->addConnection($dsn, $username, $password, $this->config, $driverOptions);
- }
- if (isset($this->config[xPDO::OPT_CONNECTIONS])) {
- $connections = $this->config[xPDO::OPT_CONNECTIONS];
- if (is_string($connections)) {
- $connections = $this->fromJSON($connections);
- }
- if (is_array($connections)) {
- foreach ($connections as $connection) {
- $this->addConnection(
- $connection['dsn'],
- $connection['username'],
- $connection['password'],
- $connection['options'],
- $connection['driverOptions']
- );
- }
- }
- }
- $initOptions = $this->getOption(xPDO::OPT_CONN_INIT, null, array());
- $this->config = array_merge($this->config, $this->getConnection($initOptions)->config);
- $this->getDriver();
- $this->setPackage('om', XPDO_CORE_PATH, $this->config[xPDO::OPT_TABLE_PREFIX]);
- if (isset($this->config[xPDO::OPT_BASE_PACKAGES]) && !empty($this->config[xPDO::OPT_BASE_PACKAGES])) {
- $basePackages= explode(',', $this->config[xPDO::OPT_BASE_PACKAGES]);
- foreach ($basePackages as $basePackage) {
- $exploded= explode(':', $basePackage, 2);
- if ($exploded) {
- $path= $exploded[1];
- $prefix= null;
- if (strpos($path, ';')) {
- $details= explode(';', $path);
- if ($details && count($details) == 2) {
- $path= $details[0];
- $prefix = $details[1];
- }
- }
- $this->addPackage($exploded[0], $path, $prefix);
- }
- }
- }
- $this->loadClass('xPDOQuery');
- $this->loadClass('xPDOObject');
- $this->loadClass('xPDOSimpleObject');
- if (isset($this->config[xPDO::OPT_BASE_CLASSES])) {
- foreach (array_keys($this->config[xPDO::OPT_BASE_CLASSES]) as $baseClass) {
- $this->loadClass($baseClass);
- }
- }
- if (isset($this->config[xPDO::OPT_CACHE_PATH])) {
- $this->cachePath = $this->config[xPDO::OPT_CACHE_PATH];
- }
- } catch (Exception $e) {
- throw new xPDOException("Could not instantiate xPDO: " . $e->getMessage());
- }
- }
- /**
- * Initialize an xPDO config array.
- *
- * @param string|array $data The config input source. Currently accepts a PHP array,
- * or a PHP string representing xPDO::OPT_TABLE_PREFIX (deprecated).
- * @return array An array of xPDO config data.
- */
- protected function initConfig($data) {
- if (is_string($data)) {
- $data= array(xPDO::OPT_TABLE_PREFIX => $data);
- } elseif (!is_array($data)) {
- $data= array(xPDO::OPT_TABLE_PREFIX => '');
- }
- return $data;
- }
- /**
- * Add an xPDOConnection instance to the xPDO connection pool.
- *
- * @param string $dsn A PDO DSN representing the connection details.
- * @param string $username The username credentials for the connection.
- * @param string $password The password credentials for the connection.
- * @param array $options An array of options for the connection.
- * @param null $driverOptions An array of PDO driver options for the connection.
- * @return boolean True if a valid connection was added.
- */
- public function addConnection($dsn, $username= '', $password= '', array $options= array(), $driverOptions= null) {
- $added = false;
- $connection= new xPDOConnection($this, $dsn, $username, $password, $options, $driverOptions);
- if ($connection instanceof xPDOConnection) {
- $this->_connections[]= $connection;
- $added= true;
- }
- return $added;
- }
- /**
- * Get an xPDOConnection from the xPDO connection pool.
- *
- * @param array $options An array of options for getting the connection.
- * @return xPDOConnection|null An xPDOConnection instance or null if no connection could be retrieved.
- */
- public function &getConnection(array $options = array()) {
- $conn =& $this->connection;
- $mutable = $this->getOption(xPDO::OPT_CONN_MUTABLE, $options, null);
- if (!($conn instanceof xPDOConnection) || ($mutable !== null && (($mutable == true && !$conn->isMutable()) || ($mutable == false && $conn->isMutable())))) {
- if (!empty($this->_connections)) {
- shuffle($this->_connections);
- $conn = reset($this->_connections);
- while ($conn) {
- if ($mutable !== null && (($mutable == true && !$conn->isMutable()) || ($mutable == false && $conn->isMutable()))) {
- $conn = next($this->_connections);
- continue;
- }
- $this->connection =& $conn;
- break;
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Could not get a valid xPDOConnection", '', __METHOD__, __FILE__, __LINE__);
- }
- }
- return $this->connection;
- }
- /**
- * Get or create a PDO connection to a database specified in the configuration.
- *
- * @param array $driverOptions An optional array of driver options to use
- * when creating the connection.
- * @param array $options An array of xPDO options for the connection.
- * @return boolean Returns true if the PDO connection was created successfully.
- */
- public function connect($driverOptions= array (), array $options= array()) {
- $connected = false;
- $this->getConnection($options);
- if ($this->connection instanceof xPDOConnection) {
- $connected = $this->connection->connect($driverOptions);
- if ($connected) {
- $this->pdo =& $this->connection->pdo;
- }
- }
- return $connected;
- }
- /**
- * Sets a specific model package to use when looking up classes.
- *
- * This package is of the form package.subpackage.subsubpackage and will be
- * added to the beginning of every xPDOObject class that is referenced in
- * xPDO methods such as {@link xPDO::loadClass()}, {@link xPDO::getObject()},
- * {@link xPDO::getCollection()}, {@link xPDOObject::getOne()}, {@link
- * xPDOObject::addOne()}, etc.
- *
- * @param string $pkg A package name to use when looking up classes in xPDO.
- * @param string $path The root path for looking up classes in this package.
- * @param string|null $prefix Provide a string to define a package-specific table_prefix.
- * @return bool
- */
- public function setPackage($pkg= '', $path= '', $prefix= null) {
- if (empty($path) && isset($this->packages[$pkg])) {
- $path= $this->packages[$pkg]['path'];
- $prefix= !is_string($prefix) && array_key_exists('prefix', $this->packages[$pkg]) ? $this->packages[$pkg]['prefix'] : $prefix;
- }
- $set= $this->addPackage($pkg, $path, $prefix);
- $this->package= $set == true ? $pkg : $this->package;
- if ($set && is_string($prefix)) $this->config[xPDO::OPT_TABLE_PREFIX]= $prefix;
- return $set;
- }
- /**
- * Adds a model package and base class path for including classes and/or maps from.
- *
- * @param string $pkg A package name to use when looking up classes/maps in xPDO.
- * @param string $path The root path for looking up classes in this package.
- * @param string|null $prefix Provide a string to define a package-specific table_prefix.
- * @return bool
- */
- public function addPackage($pkg= '', $path= '', $prefix= null) {
- $added= false;
- if (is_string($pkg) && !empty($pkg)) {
- if (!is_string($path) || empty($path)) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Invalid path specified for package: {$pkg}; using default xpdo model path: " . XPDO_CORE_PATH . 'om/');
- $path= XPDO_CORE_PATH . 'om/';
- }
- if (!is_dir($path)) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Path specified for package {$pkg} is not a valid or accessible directory: {$path}");
- } else {
- $prefix= !is_string($prefix) ? $this->config[xPDO::OPT_TABLE_PREFIX] : $prefix;
- if (!array_key_exists($pkg, $this->packages) || $this->packages[$pkg]['path'] !== $path || $this->packages[$pkg]['prefix'] !== $prefix) {
- $this->packages[$pkg]= array('path' => $path, 'prefix' => $prefix);
- $this->setPackageMeta($pkg, $path);
- }
- $added= true;
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, 'addPackage called with an invalid package name.');
- }
- return $added;
- }
- /**
- * Adds metadata information about a package and loads the xPDO::$classMap.
- *
- * @param string $pkg A package name to use when looking up classes/maps in xPDO.
- * @param string $path The root path for looking up classes in this package.
- * @return bool
- */
- public function setPackageMeta($pkg, $path = '') {
- $set = false;
- if (is_string($pkg) && !empty($pkg)) {
- $pkgPath = str_replace('.', '/', $pkg);
- $mapFile = $path . $pkgPath . '/metadata.' . $this->config['dbtype'] . '.php';
- if (file_exists($mapFile)) {
- $xpdo_meta_map = '';
- include $mapFile;
- if (!empty($xpdo_meta_map)) {
- foreach ($xpdo_meta_map as $className => $extends) {
- if (!isset($this->classMap[$className])) {
- $this->classMap[$className] = array();
- }
- $this->classMap[$className] = array_unique(array_merge($this->classMap[$className],$extends));
- }
- $set = true;
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_WARN, "Could not load package metadata for package {$pkg}.");
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, 'setPackageMeta called with an invalid package name.');
- }
- return $set;
- }
- /**
- * Gets a list of derivative classes for the specified className.
- *
- * The specified className must be xPDOObject or a derivative class.
- *
- * @param string $className The name of the class to retrieve derivatives for.
- * @return array An array of derivative classes or an empty array.
- */
- public function getDescendants($className) {
- $descendants = array();
- if (isset($this->classMap[$className])) {
- $descendants = $this->classMap[$className];
- if ($descendants) {
- foreach ($descendants as $descendant) {
- $descendants = array_merge($descendants, $this->getDescendants($descendant));
- }
- }
- }
- return $descendants;
- }
- /**
- * Load a class by fully qualified name.
- *
- * The $fqn should in the format:
- *
- * dir_a.dir_b.dir_c.classname
- *
- * which will translate to:
- *
- * XPDO_CORE_PATH/om/dir_a/dir_b/dir_c/dbtype/classname.class.php
- *
- * @param string $fqn The fully-qualified name of the class to load.
- * @param string $path An optional path to start the search from.
- * @param bool $ignorePkg True if currently loaded packages should be ignored.
- * @param bool $transient True if the class is not a persistent table class.
- * @return string|boolean The actual classname if successful, or false if
- * not.
- */
- public function loadClass($fqn, $path= '', $ignorePkg= false, $transient= false) {
- if (empty($fqn)) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "No class specified for loadClass");
- return false;
- }
- if (!$transient) {
- $typePos= strrpos($fqn, '_' . $this->config['dbtype']);
- if ($typePos !== false) {
- $fqn= substr($fqn, 0, $typePos);
- }
- }
- $pos= strrpos($fqn, '.');
- if ($pos === false) {
- $class= $fqn;
- if ($transient) {
- $fqn= strtolower($class);
- } else {
- $fqn= $this->config['dbtype'] . '.' . strtolower($class);
- }
- } else {
- $class= substr($fqn, $pos +1);
- if ($transient) {
- $fqn= substr($fqn, 0, $pos) . '.' . strtolower($class);
- } else {
- $fqn= substr($fqn, 0, $pos) . '.' . $this->config['dbtype'] . '.' . strtolower($class);
- }
- }
- // check if class exists
- if (!$transient && isset ($this->map[$class])) return $class;
- $included= class_exists($class, false);
- if ($included) {
- if ($transient || (!$transient && isset ($this->map[$class]))) {
- return $class;
- }
- }
- $classname= $class;
- if (!empty($path) || $ignorePkg) {
- $class= $this->_loadClass($class, $fqn, $included, $path, $transient);
- } elseif (isset ($this->packages[$this->package])) {
- $pqn= $this->package . '.' . $fqn;
- if (!$pkgClass= $this->_loadClass($class, $pqn, $included, $this->packages[$this->package]['path'], $transient)) {
- foreach ($this->packages as $pkg => $pkgDef) {
- if ($pkg === $this->package) continue;
- $pqn= $pkg . '.' . $fqn;
- if ($pkgClass= $this->_loadClass($class, $pqn, $included, $pkgDef['path'], $transient)) {
- break;
- }
- }
- }
- $class= $pkgClass;
- } else {
- $class= false;
- }
- if ($class === false) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Could not load class: {$classname} from {$fqn}.");
- }
- return $class;
- }
- protected function _loadClass($class, $fqn, $included= false, $path= '', $transient= false) {
- if (empty($path)) $path= XPDO_CORE_PATH;
- if (!$included) {
- /* turn to filesystem path and enforce all lower-case paths and filenames */
- $fqcn= str_replace('.', '/', $fqn) . '.class.php';
- /* include class */
- if (!file_exists($path . $fqcn)) return false;
- if (!$rt= include_once ($path . $fqcn)) {
- $this->log(xPDO::LOG_LEVEL_WARN, "Could not load class: {$class} from {$path}{$fqcn}");
- $class= false;
- }
- }
- if ($class && !$transient && !isset ($this->map[$class])) {
- $mapfile= strtr($fqn, '.', '/') . '.map.inc.php';
- if (file_exists($path . $mapfile)) {
- $xpdo_meta_map= & $this->map;
- $rt= include ($path . $mapfile);
- if (!$rt || !isset($this->map[$class])) {
- $this->log(xPDO::LOG_LEVEL_WARN, "Could not load metadata map {$mapfile} for class {$class} from {$fqn}");
- } else {
- if (!array_key_exists('fieldAliases', $this->map[$class])) {
- $this->map[$class]['fieldAliases'] = array();
- }
- }
- }
- }
- return $class;
- }
- /**
- * Get an xPDO configuration option value by key.
- *
- * @param string $key The option key.
- * @param array $options A set of options to override those from xPDO.
- * @param mixed $default An optional default value to return if no value is found.
- * @return mixed The configuration option value.
- */
- public function getOption($key, $options = null, $default = null, $skipEmpty = false) {
- $option = null;
- if (is_string($key) && !empty($key)) {
- $found = false;
- if (isset($options[$key])) {
- $found = true;
- $option = $options[$key];
- }
- if ((!$found || ($skipEmpty && $option === '')) && isset($this->config[$key])) {
- $found = true;
- $option = $this->config[$key];
- }
- if (!$found || ($skipEmpty && $option === ''))
- $option = $default;
- }
- else if (is_array($key)) {
- if (!is_array($option)) {
- $default = $option;
- $option = array();
- }
- foreach($key as $k) {
- $option[$k] = $this->getOption($k, $options, $default);
- }
- }
- else
- $option = $default;
- return $option;
- }
- /**
- * Sets an xPDO configuration option value.
- *
- * @param string $key The option key.
- * @param mixed $value A value to set for the given option key.
- */
- public function setOption($key, $value) {
- $this->config[$key]= $value;
- }
- /**
- * Call a static method from a valid package class with arguments.
- *
- * Will always search for database-specific class files first.
- *
- * @param string $class The name of a class to to get the static method from.
- * @param string $method The name of the method you want to call.
- * @param array $args An array of arguments for the method.
- * @param boolean $transient Indicates if the class has dbtype derivatives. Set to true if you
- * want to use on classes not derived from xPDOObject.
- * @return mixed|null The callback method's return value or null if no valid method is found.
- */
- public function call($class, $method, array $args = array(), $transient = false) {
- $return = null;
- $callback = '';
- if ($transient) {
- $className = $this->loadClass($class, '', false, true);
- if ($className) {
- $callback = array($className, $method);
- }
- } else {
- $className = $this->loadClass($class);
- if ($className) {
- $className .= '_' . $this->getOption('dbtype');
- $callback = array($className, $method);
- }
- }
- if (!empty($callback) && is_callable($callback)) {
- try {
- $return = call_user_func_array($callback, $args);
- } catch (Exception $e) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "An exception occurred calling {$className}::{$method}() - " . $e->getMessage());
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "{$class}::{$method}() is not a valid static method.");
- }
- return $return;
- }
- /**
- * Creates a new instance of a specified class.
- *
- * All new objects created with this method are transient until {@link
- * xPDOObject::save()} is called the first time and is reflected by the
- * {@link xPDOObject::$_new} property.
- *
- * @param string $className Name of the class to get a new instance of.
- * @param array $fields An associated array of field names/values to
- * populate the object with.
- * @return object|null A new instance of the specified class, or null if a
- * new object could not be instantiated.
- */
- public function newObject($className, $fields= array ()) {
- $instance= null;
- if ($className= $this->loadClass($className)) {
- $className .= '_' . $this->config['dbtype'];
- if ($instance= new $className ($this)) {
- if (is_array($fields) && !empty ($fields)) {
- $instance->fromArray($fields);
- }
- }
- }
- return $instance;
- }
- /**
- * Finds the class responsible for loading instances of the specified class.
- *
- * @deprecated Use call() instead.
- * @param string $className The name of the class to find a loader for.
- * @param string $method Indicates the specific loader method to use,
- * loadCollection or loadObject (or other public static methods).
- * @return callable A callable loader function.
- */
- public function getObjectLoader($className, $method) {
- $loader = false;
- if (isset($this->config[xPDO::OPT_LOADER_CLASSES]) && is_array($this->config[xPDO::OPT_LOADER_CLASSES])) {
- if ($ancestry = $this->getAncestry($className, true)) {
- if ($callbacks = array_intersect($ancestry, $this->config[xPDO::OPT_LOADER_CLASSES])) {
- if ($loaderClass = reset($callbacks)) {
- $loader = array($loaderClass, $method);
- while (!is_callable($loader) && $loaderClass = next($callbacks)) {
- $loader = array($loaderClass, $method);
- }
- }
- }
- }
- }
- if (!is_callable($loader)) {
- $loader = array('xPDOObject', $method);
- }
- return $loader;
- }
- /**
- * Retrieves a single object instance by the specified criteria.
- *
- * The criteria can be a primary key value, and array of primary key values
- * (for multiple primary key objects) or an {@link xPDOCriteria} object. If
- * no $criteria parameter is specified, no class is found, or an object
- * cannot be located by the supplied criteria, null is returned.
- *
- * @uses xPDOObject::load()
- * @param string $className Name of the class to get an instance of.
- * @param mixed $criteria Primary key of the record or a xPDOCriteria object.
- * @param mixed $cacheFlag If an integer value is provided, this specifies
- * the time to live in the object cache; if cacheFlag === false, caching is
- * ignored for the object and if cacheFlag === true, the object will live in
- * cache indefinitely.
- * @return object|null An instance of the class, or null if it could not be
- * instantiated.
- */
- public function getObject($className, $criteria= null, $cacheFlag= true) {
- $instance= null;
- $this->sanitizePKCriteria($className, $criteria);
- if ($criteria !== null) {
- $instance = $this->call($className, 'load', array(& $this, $className, $criteria, $cacheFlag));
- }
- return $instance;
- }
- /**
- * Retrieves a collection of xPDOObjects by the specified xPDOCriteria.
- *
- * @uses xPDOObject::loadCollection()
- * @param string $className Name of the class to search for instances of.
- * @param object|array|string $criteria An xPDOCriteria object or an array
- * search expression.
- * @param mixed $cacheFlag If an integer value is provided, this specifies
- * the time to live in the result set cache; if cacheFlag === false, caching
- * is ignored for the collection and if cacheFlag === true, the objects will
- * live in cache until flushed by another process.
- * @return array|null An array of class instances retrieved.
- */
- public function getCollection($className, $criteria= null, $cacheFlag= true) {
- return $this->call($className, 'loadCollection', array(& $this, $className, $criteria, $cacheFlag));
- }
- /**
- * Retrieves an iterable representation of a collection of xPDOObjects.
- *
- * @param string $className Name of the class to search for instances of.
- * @param mixed $criteria An xPDOCriteria object or representation.
- * @param bool $cacheFlag If an integer value is provided, this specifies
- * the time to live in the result set cache; if cacheFlag === false, caching
- * is ignored for the collection and if cacheFlag === true, the objects will
- * live in cache until flushed by another process.
- * @return xPDOIterator An iterable representation of a collection.
- */
- public function getIterator($className, $criteria= null, $cacheFlag= true) {
- return new xPDOIterator($this, array('class' => $className, 'criteria' => $criteria, 'cacheFlag' => $cacheFlag));
- }
- /**
- * Update field values across a collection of xPDOObjects.
- *
- * @param string $className Name of the class to update fields of.
- * @param array $set An associative array of field/value pairs representing the updates to make.
- * @param mixed $criteria An xPDOCriteria object or representation.
- * @return bool|int The number of instances affected by the update or false on failure.
- */
- public function updateCollection($className, array $set, $criteria= null) {
- $affected = false;
- if ($this->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
- $query = $this->newQuery($className);
- if ($query && !empty($set)) {
- $query->command('UPDATE');
- $query->set($set);
- if (!empty($criteria)) $query->where($criteria);
- if ($query->prepare()) {
- $affected = $this->exec($query->toSQL());
- if ($affected === false) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Error updating {$className} instances using query " . $query->toSQL(), '', __METHOD__, __FILE__, __LINE__);
- } else {
- if ($this->getOption(xPDO::OPT_CACHE_DB)) {
- $relatedClasses = array($query->getTableClass());
- $related = array_merge($this->getAggregates($className), $this->getComposites($className));
- foreach ($related as $relatedAlias => $relatedMeta) {
- $relatedClasses[] = $relatedMeta['class'];
- }
- $relatedClasses = array_unique($relatedClasses);
- foreach ($relatedClasses as $relatedClass) {
- $this->cacheManager->delete($relatedClass, array(
- xPDO::OPT_CACHE_KEY => $this->getOption('cache_db_key', null, 'db'),
- xPDO::OPT_CACHE_HANDLER => $this->getOption(xPDO::OPT_CACHE_DB_HANDLER, null, $this->getOption(xPDO::OPT_CACHE_HANDLER, null, 'cache.xPDOFileCache')),
- xPDO::OPT_CACHE_FORMAT => (integer) $this->getOption('cache_db_format', null, $this->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)),
- xPDO::OPT_CACHE_PREFIX => $this->getOption('cache_db_prefix', null, xPDOCacheManager::CACHE_DIR),
- 'multiple_object_delete' => true
- ));
- }
- }
- $callback = $this->getOption(xPDO::OPT_CALLBACK_ON_SAVE);
- if ($callback && is_callable($callback)) {
- call_user_func($callback, array('className' => $className, 'criteria' => $query, 'object' => null));
- }
- }
- }
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Could not get connection for writing data", '', __METHOD__, __FILE__, __LINE__);
- }
- return $affected;
- }
- /**
- * Remove an instance of the specified className by a supplied criteria.
- *
- * @param string $className The name of the class to remove an instance of.
- * @param mixed $criteria Valid xPDO criteria for selecting an instance.
- * @return boolean True if the instance is successfully removed.
- */
- public function removeObject($className, $criteria) {
- $removed= false;
- if ($this->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
- if ($this->getCount($className, $criteria) === 1) {
- if ($query= $this->newQuery($className)) {
- $query->command('DELETE');
- $query->where($criteria);
- if ($query->prepare()) {
- if ($this->exec($query->toSQL()) !== 1) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "xPDO->removeObject - Error deleting {$className} instance using query " . $query->toSQL());
- } else {
- $removed= true;
- if ($this->getOption(xPDO::OPT_CACHE_DB)) {
- $this->cacheManager->delete(xPDOCacheManager::CACHE_DIR . $query->getAlias(), array('multiple_object_delete' => true));
- }
- $callback = $this->getOption(xPDO::OPT_CALLBACK_ON_REMOVE);
- if ($callback && is_callable($callback)) {
- call_user_func($callback, array('className' => $className, 'criteria' => $query));
- }
- }
- }
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_WARN, "xPDO->removeObject - {$className} instance to remove not found!");
- if ($this->getDebug() === true) $this->log(xPDO::LOG_LEVEL_DEBUG, "xPDO->removeObject - {$className} instance to remove not found using criteria " . print_r($criteria, true));
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Could not get connection for writing data", '', __METHOD__, __FILE__, __LINE__);
- }
- return $removed;
- }
- /**
- * Remove a collection of instances by the supplied className and criteria.
- *
- * @param string $className The name of the class to remove a collection of.
- * @param mixed $criteria Valid xPDO criteria for selecting a collection.
- * @return boolean|integer False if the remove encounters an error, otherwise an integer value
- * representing the number of rows that were removed.
- */
- public function removeCollection($className, $criteria) {
- $removed= false;
- if ($this->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
- if ($query= $this->newQuery($className)) {
- $query->command('DELETE');
- $query->where($criteria);
- if ($query->prepare()) {
- $removed= $this->exec($query->toSQL());
- if ($removed === false) {
- $this->log(xPDO::LOG_LEVEL_ERROR, "xPDO->removeCollection - Error deleting {$className} instances using query " . $query->toSQL());
- } else {
- if ($this->getOption(xPDO::OPT_CACHE_DB)) {
- $this->cacheManager->delete(xPDOCacheManager::CACHE_DIR . $query->getAlias(), array('multiple_object_delete' => true));
- }
- $callback = $this->getOption(xPDO::OPT_CALLBACK_ON_REMOVE);
- if ($callback && is_callable($callback)) {
- call_user_func($callback, array('className' => $className, 'criteria' => $query));
- }
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "xPDO->removeCollection - Error preparing statement to delete {$className} instances using query: {$query->toSQL()}");
- }
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "Could not get connection for writing data", '', __METHOD__, __FILE__, __LINE__);
- }
- return $removed;
- }
- /**
- * Retrieves a count of xPDOObjects by the specified xPDOCriteria.
- *
- * @param string $className Class of xPDOObject to count instances of.
- * @param mixed $criteria Any valid xPDOCriteria object or expression.
- * @return integer The number of instances found by the criteria.
- */
- public function getCount($className, $criteria = null) {
- $count = 0;
- if ($query = $this->newQuery($className, $criteria)) {
- $stmt = null;
- $expr = '*';
- if ($pk = $this->getPK($className)) {
- if (!is_array($pk)) {
- $pk = array($pk);
- }
- $expr = $this->getSelectColumns($className, $query->getAlias(), '', $pk);
- }
- if (isset($query->query['columns'])) {
- $query->query['columns'] = array();
- }
- if (!empty($query->query['groupby']) || !empty($query->query['having'])) {
- $query->select($expr);
- if ($query->prepare()) {
- $countQuery = new xPDOCriteria($this, "SELECT COUNT(*) FROM ({$query->toSQL(false)}) cq", $query->bindings, $query->cacheFlag);
- $stmt = $countQuery->prepare();
- }
- } else {
- $query->select(array("COUNT(DISTINCT {$expr})"));
- $stmt = $query->prepare();
- }
- if ($stmt && $stmt->execute()) {
- $count = intval($stmt->fetchColumn());
- }
- }
- return $count;
- }
- /**
- * Retrieves an xPDOObject instance with specified related objects.
- *
- * @uses xPDO::getCollectionGraph()
- * @param string $className The name of the class to return an instance of.
- * @param string|array $graph A related object graph in array or JSON
- * format, e.g. array('relationAlias'=>array('subRelationAlias'=>array()))
- * or {"relationAlias":{"subRelationAlias":{}}}. Note that the empty arrays
- * are necessary in order for the relation to be recognized.
- * @param mixed $criteria A valid xPDOCriteria instance or expression.
- * @param boolean|integer $cacheFlag Indicates if the result set should be
- * cached, and optionally for how many seconds.
- * @return object The object instance with related objects from the graph
- * hydrated, or null if no instance can be located by the criteria.
- */
- public function getObjectGraph($className, $graph, $criteria= null, $cacheFlag= true) {
- $object= null;
- if ($collection= $this->getCollectionGraph($className, $graph, $criteria, $cacheFlag)) {
- if (!count($collection) === 1) {
- $this->log(xPDO::LOG_LEVEL_WARN, 'getObjectGraph criteria returned more than one instance.');
- }
- $object= reset($collection);
- }
- return $object;
- }
- /**
- * Retrieves a collection of xPDOObject instances with related objects.
- *
- * @uses xPDOQuery::bindGraph()
- * @param string $className The name of the class to return a collection of.
- * @param string|array $graph A related object graph in array or JSON
- * format, e.g. array('relationAlias'=>array('subRelationAlias'=>array()))
- * or {"relationAlias":{"subRelationAlias":{}}}. Note that the empty arrays
- * are necessary in order for the relation to be recognized.
- * @param mixed $criteria A valid xPDOCriteria instance or condition string.
- * @param boolean $cacheFlag Indicates if the result set should be cached.
- * @return array An array of instances matching the criteria with related
- * objects from the graph hydrated. An empty array is returned when no
- * matches are found.
- */
- public function getCollectionGraph($className, $graph, $criteria= null, $cacheFlag= true) {
- return $this->call($className, 'loadCollectionGraph', array(& $this, $className, $graph, $criteria, $cacheFlag));
- }
- /**
- * Execute a PDOStatement and get a single column value from the first row of the result set.
- *
- * @param PDOStatement $stmt A prepared PDOStatement object ready to be executed.
- * @param null|integer $column 0-indexed number of the column you wish to retrieve from the row. If
- * null or no value is supplied, it fetches the first column.
- * @return mixed The value of the specified column from the first row of the result set, or null.
- */
- public function getValue($stmt, $column= null) {
- $value = null;
- if (is_object($stmt) && $stmt instanceof PDOStatement) {
- $tstart = microtime(true);
- if ($stmt->execute()) {
- $this->queryTime += microtime(true) - $tstart;
- $this->executedQueries++;
- $value= $stmt->fetchColumn($column);
- $stmt->closeCursor();
- } else {
- $this->queryTime += microtime(true) - $tstart;
- $this->executedQueries++;
- $this->log(xPDO::LOG_LEVEL_ERROR, "Error " . $stmt->errorCode() . " executing statement: \n" . print_r($stmt->errorInfo(), true), '', __METHOD__, __FILE__, __LINE__);
- }
- } else {
- $this->log(xPDO::LOG_LEVEL_ERROR, "No valid PDOStatement provided to getValue", '', __METHOD__, __FILE__, __LINE__);
- }
- return $value;
- }
- /**
- * Convert any valid criteria into an xPDOQuery instance.
- *
- * @todo Get criteria pre-defined in an {@link xPDOObject} class metadata
- * definition by name.
- *
- * @todo Define callback functions as an alternative to retreiving criteria
- * sql and/or bindings from the metadata.
- *
- * @param string $className The class to get predefined criteria for.
- * @param string $type The type of criteria to get (you can define any
- * type you want, but 'object' and 'collection' are the typical criteria
- * for retrieving single and multiple instances of an object).
- * @param boolean|integer $cacheFlag Indicates if the result is cached and
- * optionally for how many seconds.
- * @return xPDOCriteria A criteria object or null if not found.
- */
- public function getCriteria($className, $type= null, $cacheFlag= true) {
- return $this->newQuery($className, $type, $cacheFlag);
- }
- /**
- * Validate and return the type of a specified criteria variable.
- *
- * @param mixed $criteria An xPDOCriteria instance or any valid criteria variable.
- * @return string|null The type of valid criteria passed, or null if the criteria is not valid.
- */
- public function getCriteriaType($criteria) {
- $type = gettype($criteria);
- if ($type === 'object') {
- $…
Large files files are truncated, but you can click here to view the full file