| Current File : /home/jvzmxxx/wiki1/extensions/Flow/includes/Data/ObjectLocator.php |
<?php
namespace Flow\Data;
use Flow\DbFactory;
use Flow\Data\Utils\RawSql;
use Flow\Exception\NoIndexException;
use Flow\Model\UUID;
use FormatJson;
/**
* Denormalized indexes that are query-only. The indexes used here must
* be provided to some ObjectManager as a lifecycleHandler to receive
* update events.
*/
class ObjectLocator {
/*
* @var ObjectMapper
*/
protected $mapper;
/**
* @var ObjectStorage
*/
protected $storage;
/**
* @var Index[]
*/
protected $indexes;
/**
* Database factory (only for addQuotes)
*
* @var DbFactory
*/
protected $dbFactory;
/**
* @var LifecycleHandler[]
*/
protected $lifecycleHandlers;
/**
* @param ObjectMapper $mapper
* @param ObjectStorage $storage
* @param DbFactory $dbFactory
* @param Index[] $indexes
* @param LifecycleHandler[] $lifecycleHandlers
*/
public function __construct( ObjectMapper $mapper, ObjectStorage $storage, DbFactory $dbFactory, array $indexes = array(), array $lifecycleHandlers = array() ) {
$this->mapper = $mapper;
$this->storage = $storage;
$this->indexes = $indexes;
$this->dbFactory = $dbFactory;
$this->lifecycleHandlers = array_merge( $indexes, $lifecycleHandlers );
}
public function getMapper() {
return $this->mapper;
}
public function find( array $attributes, array $options = array() ) {
$result = $this->findMulti( array( $attributes ), $options );
return $result ? reset( $result ) : array();
}
/**
* All queries must be against the same index. Results are equivalent to
* array_map, maintaining order and key relationship between input $queries
* and $result.
*
* @param array $queries
* @param array $options
* @return array[]
*/
public function findMulti( array $queries, array $options = array() ) {
if ( !$queries ) {
return array();
}
$keys = array_keys( reset( $queries ) );
if ( isset( $options['sort'] ) && !is_array( $options['sort'] ) ) {
$options['sort'] = ObjectManager::makeArray( $options['sort'] );
}
try {
$index = $this->getIndexFor( $keys, $options );
$res = $index->findMulti( $queries, $options );
} catch ( NoIndexException $e ) {
if ( array_search( 'topic_root_id', $keys ) ) {
wfDebugLog(
'Flow',
__METHOD__ . ': '
. json_encode( $keys ) . ' : '
. json_encode( $options ) . ' : '
. json_encode( array_map( 'get_class', $this->indexes ) )
);
\MWExceptionHandler::logException( $e );
} else {
wfDebugLog( 'FlowDebug', __METHOD__ . ': ' . $e->getMessage() );
}
$res = $this->storage->findMulti( $this->convertToDbQueries( $queries, $options ), $this->convertToDbOptions( $options ) );
}
$output = array();
foreach( $res as $index => $queryOutput ) {
foreach ( $queryOutput as $k => $v ) {
if ( $v ) {
$output[$index][$k] = $this->load( $v );
}
}
}
return $output;
}
/**
* Returns a boolean true/false if the find()-operation for the given
* attributes has already been resolves and doesn't need to query any
* outside cache/database.
* Determining if a find() has not yet been resolved may be useful so that
* additional data may be loaded at once.
*
* @param array $attributes Attributes to find()
* @param array[optional] $options Options to find()
* @return bool
*/
public function found( array $attributes, array $options = array() ) {
return $this->foundMulti( array( $attributes ), $options );
}
/**
* Returns a boolean true/false if the findMulti()-operation for the given
* attributes has already been resolves and doesn't need to query any
* outside cache/database.
* Determining if a find() has not yet been resolved may be useful so that
* additional data may be loaded at once.
*
* @param array $queries Queries to findMulti()
* @param array[optional] $options Options to findMulti()
* @return bool
*/
public function foundMulti( array $queries, array $options = array() ) {
if ( !$queries ) {
return true;
}
$keys = array_keys( reset( $queries ) );
if ( isset( $options['sort'] ) && !is_array( $options['sort'] ) ) {
$options['sort'] = ObjectManager::makeArray( $options['sort'] );
}
foreach( $queries as $key => $value ) {
$queries[$key] = UUID::convertUUIDs( $value, 'alphadecimal' );
}
try {
$index = $this->getIndexFor( $keys, $options );
$res = $index->foundMulti( $queries, $options );
return $res;
} catch ( NoIndexException $e ) {
wfDebugLog( 'FlowDebug', __METHOD__ . ': ' . $e->getMessage() );
}
return false;
}
public function getPrimaryKeyColumns() {
return $this->storage->getPrimaryKeyColumns();
}
public function get( $id ) {
$result = $this->getMulti( array( $id ) );
return $result ? reset( $result ) : null;
}
// Just a helper to find by primary key
//
// Be careful with regards to order on composite primary keys,
// must be in same order as provided to the storage implementation.
public function getMulti( array $objectIds ) {
if ( !$objectIds ) {
return array();
}
$primaryKey = $this->storage->getPrimaryKeyColumns();
$queries = array();
$retval = null;
foreach ( $objectIds as $id ) {
// check internal cache
$query = array_combine( $primaryKey, ObjectManager::makeArray( $id ) );
$obj = $this->mapper->get( $query );
if ( $obj === null ) {
$queries[] = $query;
} else {
$retval[] = $obj;
}
}
if ( $queries ) {
$res = $this->findMulti( $queries );
if ( $res ) {
foreach ( $res as $row ) {
// primary key is unique, but indexes still return their results as array
// to be consistent. undo that for a flat result array
$retval[] = reset( $row );
}
}
}
return $retval;
}
/**
* Returns a boolean true/false if the get()-operation for the given
* attributes has already been resolves and doesn't need to query any
* outside cache/database.
* Determining if a find() has not yet been resolved may be useful so that
* additional data may be loaded at once.
*
* @param string|integer $id Id to get()
* @return bool
*/
public function got( $id ) {
return $this->gotMulti( array( $id ) );
}
/**
* Returns a boolean true/false if the getMulti()-operation for the given
* attributes has already been resolves and doesn't need to query any
* outside cache/database.
* Determining if a find() has not yet been resolved may be useful so that
* additional data may be loaded at once.
*
* @param array $objectIds Ids to getMulti()
* @return bool
*/
public function gotMulti( array $objectIds ) {
if ( !$objectIds ) {
return true;
}
$primaryKey = $this->storage->getPrimaryKeyColumns();
$queries = array();
foreach ( $objectIds as $id ) {
$query = array_combine( $primaryKey, ObjectManager::makeArray( $id ) );
$query = UUID::convertUUIDs( $query, 'alphadecimal' );
if ( !$this->mapper->get( $query ) ) {
$queries[] = $query;
}
}
if ( $queries && $this->mapper instanceof Mapper\CachingObjectMapper ) {
return false;
}
return $this->foundMulti( $queries );
}
public function clear() {
// nop, we don't store anything
}
/**
* @param array $keys
* @param array $options
* @return Index
* @throws NoIndexException
*/
public function getIndexFor( array $keys, array $options = array() ) {
sort( $keys );
/** @var Index|null $current */
$current = null;
foreach ( $this->indexes as $index ) {
// @var Index $index
if ( !$index->canAnswer( $keys, $options ) ) {
continue;
}
// make sure at least some index is picked
if ( $current === null ) {
$current = $index;
// Find the smallest matching index
} else if ( isset( $options['limit'] ) ) {
$current = $index->getLimit() < $current->getLimit() ? $index : $current;
// if no limit specified, find biggest matching index
} else {
$current = $index->getLimit() > $current->getLimit() ? $index : $current;
}
}
if ( $current === null ) {
$count = count( $this->indexes );
throw new NoIndexException(
"No index (out of $count) available to answer query for " . implode( ", ", $keys ) .
' with options ' . FormatJson::encode( $options ), 'no-index'
);
}
return $current;
}
protected function load( $row ) {
$object = $this->mapper->fromStorageRow( $row );
foreach ( $this->lifecycleHandlers as $handler ) {
$handler->onAfterLoad( $object, $row );
}
return $object;
}
/**
* Convert index options to db equivalent options
*/
protected function convertToDbOptions( $options ) {
$dbOptions = $orderBy = array();
$order = '';
if ( isset( $options['limit'] ) ) {
$dbOptions['LIMIT'] = (int)$options['limit'];
}
if ( isset( $options['order'] ) ) {
$order = ' ' . $options['order'];
}
if ( isset( $options['sort'] ) ) {
foreach ( $options['sort'] as $val ) {
$orderBy[] = $val . $order;
}
}
if ( $orderBy ) {
$dbOptions['ORDER BY'] = $orderBy;
}
return $dbOptions;
}
/**
* Uses options to figure out conditions to add to the DB queries.
*
* @param array $queries Array of queries, with each element an array of attributes
* @param array $options Options for queries
* @return array Queries for BasicDbStorage class
*/
protected function convertToDbQueries( $queries, $options ) {
if ( isset( $options['offset-id'] ) &&
isset( $options['sort'] ) && count( $options['sort'] ) === 1 &&
preg_match( '/_id$/', $options['sort'][0] ) ) {
if ( !$options['offset-id'] instanceof UUID ) {
$options['offset-id'] = UUID::create( $options['offset-id'] );
}
if ( $options['order'] === 'ASC' ) {
$operator = '>';
} else {
$operator = '<';
}
if ( isset( $options['offset-include'] ) && $options['offset-include'] ) {
$operator .= '=';
}
$dbr = $this->dbFactory->getDB( DB_SLAVE );
$condition = new RawSql( $options['sort'][0] . ' ' . $operator . ' ' . $dbr->addQuotes( $options['offset-id']->getBinary() ) );
foreach( $queries as &$query ) {
$query[] = $condition;
}
}
return $queries;
}
}