<?php
/*******************************************************************************
* Copyright 2008 Rafael Marques Martins
*
* This file is part of SQLReactor.
*
* SQLReactor 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.
*
* SQLReactor 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 SQLReactor; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*******************************************************************************/
require_once( 'SQLReactor/SQLReactorConnection.php' );
require_once( 'SQLReactor/engines/default/SQLReactorDepTree.php' );
ini_set( 'track_errors', true );
class SQLReactor{
public static $_baseClass = 'SQLReactor';
public $_columnNames;
public $_attributeNames;
public $_connection;
protected $_mapped = false;
public $_table;
public $_primaryKey = array();
private $_unique = array();
private $_indexes = array();
public $_properties = array();
public $_ignoreSuffix = "";
public static $_config = null;
private static $_defaultConn = null;
private static $_mappingHistory = null;
public $_serialCol = null;
public $_serialAttribute = null;
public static $_cache = null;
final public function __clone(){
$this->_table = clone $this->_table;
$this->_table->class = &$this;
$this->_columnNames = clone $this->_columnNames;
$this->_attributeNames = clone $this->_attributeNames;
}
final public static function setDefaultConnection( &$conn ){
SQLReactor::$_defaultConn = $conn;
}
final public function map(){
$className = get_class( $this );
$this->_table = new SQLReactorTable( $this, $this->_properties[ 'tableName' ] );
if( ! $this->_primaryKey ){
$this->id();
}
$this->_table->primaryKey = $this->_primaryKey;
$this->_table->unique = $this->_unique;
$this->_table->indexes = $this->_indexes;
if( count( $this->_table->primaryKey ) == 1 && $this->_table->primaryKey[ 0 ] == 'id' ){
$columns = array(
'id' => $this->_properties[ 'idCol' ]
);
}else{
$columns = array();
}
$columns = array_merge( $columns, get_object_vars( $this ) );
foreach( $columns as $name => $value ){
if( preg_match( "/^_/", $name ) ) continue;
if( $value[ 'type' ] == 'ForeignKey' ){
$valueCopy = $value;
unset( $valueCopy[ 'config' ][ 'name' ] );
$column = $this->_addColumn( $name, $valueCopy );
$this->_addColumn( "{$name}Id", array(
'type' => 'IntCol',
'config' => array(
'name' => $value[ 'config' ][ 'name' ],
'notNull' => $value[ 'config' ][ 'notNull' ]
)
) );
}else if( $value[ 'type' ] == 'Backref' ){
$column = $this->_addColumn( $name, $value );
}else{
$column = $this->_addColumn( $name, $value );
}
unset( $this->$name );
}
$this->_mapped = true;
if( is_null( SQLReactor::$_mappingHistory ) ){
SQLReactor::$_mappingHistory = new stdClass();
}
if( is_null( SQLReactor::$_mappingHistory->$className ) ){
SQLReactor::$_mappingHistory->$className = clone $this;
}
}
protected function _addColumn( $name, $value ){
$columnClassName = "{$value['type']}_{$this->_connection->type}";
$column = new $columnClassName( $this->_table, $name, $value[ 'config' ] );
if( $column instanceof SerialCol ){
$column->setSequence();
$this->_serialCol = &$column;
$this->_serialAttribute = $name;
}
$this->_table->columns->$name = $column;
$this->_columnNames->$name = $column->name;
$this->_attributeNames->{$column->name} = $name;
return $column;
}
protected function _setAttribute( $attribute, $value ){
if( !isset( $this->_table->columns->$attribute ) ){
$className = get_class( $this );
throw new Exception( "Invalid attribute \"{$attribute}\" for class \"{$className}\"" );
}
$magicMethodName = "__set" . ucfirst( $attribute );
if( method_exists( $this, $magicMethodName ) ){
$value = $this->$magicMethodName( $value );
}
$this->_table->columns->$attribute->setValueFromPHP( $value );
$this->_table->columns->$attribute->changed = true;
}
protected function _getAttribute( $attribute ){
if( !isset( $this->_table->columns->$attribute ) ){
$className = get_class( $this );
throw new Exception( "Invalid attribute \"{$attribute}\" for class \"{$className}\"" );
}
$value = $this->_table->columns->$attribute->getValueToPHP();
$magicMethodName = "__get" . ucfirst( $attribute );
if( method_exists( $this, $magicMethodName ) ){
$value = $this->$magicMethodName( $value );
}
return $value;
}
public function __set( $attribute, $value ){
if( !$this->_mapped ){
$this->$attribute = $value;
return;
}
$this->_setAttribute( $attribute, $value );
}
public function __get( $attribute ){
return $this->_getAttribute( $attribute );
}
public function __call( $method, $arguments ){
if( substr( $method, 0, 3 ) == 'set' ){
$attribute = substr( $method, 3 );
$attribute = isset( $this->_columnNames->$attribute ) ? $attribute : ( strtolower( $attribute[0] ) . substr( $attribute, 1 ) );
$this->_setAttribute( $attribute, $arguments[ 0 ] );
}else if ( substr( $method, 0, 3 ) == 'get' ){
$attribute = substr( $method, 3 );
$attribute = isset( $this->_columnNames->$attribute ) ? $attribute : ( strtolower( $attribute[0] ) . substr( $attribute, 1 ) );
return $this->_getAttribute( $attribute );
}
}
final public function __construct( $id = null ){
$this->_properties = array();
$this->_attributeNames = new stdClass();
$this->_columnNames = new stdClass();
$this->connection( SQLReactor::$_defaultConn );
$this->__map();
$this->map();
if( $id ){
$this->bring( $id );
}
}
final public function init(){
SQLReactor::loadConfig();
}
final public function loadConfig(){
$_CONFIG = array();
require_once "SQLReactor/config/config.php";
SQLReactor::$_config = $_CONFIG;
SQLReactor::configCache();
}
final public function configCache(){
$_errorServerConnect = array();
if( SQLReactor::$_config['cache']['active'] ){
SQLReactor::$_cache['active'] = true;
switch( SQLReactor::$_config['cache']['engine'] ){
case 'memcache':
SQLReactor::$_cache['engine'] = new Memcache();
$qtdServers = count( SQLReactor::$_config['cache']['memcache']['server'] );
if( $qtdServers > 0 )
{
for( $i = 0; $i < $qtdServers; $i++ ){
SQLReactor::$_cache['engine']->connect(SQLReactor::$_config['cache']['memcache']['server'][$i]['uri']
, SQLReactor::$_config['cache']['memcache']['server'][$i]['port']);
if( isset($php_errormsg) )
{
$msgError = $php_errormsg;
$error = array();
$error['server'] = SQLReactor::$_config['cache']['memcache']['server'][$i];
$error['msgError'] = $msgError;
$_errorServerConnect[] = $error;
}
}
if( count($_errorServerConnect) == $qtdServers ){
SQLReactor::$_cache['active'] = false;
unset(SQLReactor::$_cache['engine']); // remove the instance of free memory for Memcache
}
}
else
{
throw new Exception('No server Memcache informed in the configuration. Disable the cache or add at least one server Memcache.');
}
break;
default:
throw new Exception('No support for this cache engine.');
break;
}
}
}
final public function bring( $id ){
$copy = $this->getById( $id );
$cpArgs = get_object_vars( $copy );
if( !$cpArgs ) return;
foreach( $cpArgs as $attrName => $attr ){
$this->$attrName = & $copy->$attrName;
}
}
final public function table( $name ){
$this->_properties[ 'tableName' ] = $name;
}
final public function connection( &$connection ){
$this->_connection = $connection;
}
final public function getById( $arg1 = null, $arg2 = null ){
if( is_string( $arg1 ) && !is_numeric( $arg1 ) && class_exists( $arg1, true ) ){
$obj = new $arg1();
$id = $arg2;
}else{
$obj = &$this;
$id = $arg1;
}
if( $obj->_serialAttribute ){
$attr = $obj->_serialAttribute;
}else{
$attr = 'id';
}
$params = array(
'filter' => array(
array( $attr, $id )
)
);
$list = $obj->getList( $params );
return $list[ 0 ];
}
final public function get( $arg1 = null, $arg2 = null ){
if( is_string( $arg1 ) && class_exists( $arg1, true ) ){
$obj = new $arg1();
$params = $arg2;
}else{
$obj = &$this;
$params = $arg1;
}
$list = $obj->getList( $params );
if( count( $list ) > 1 ){
throw new Exception( "get method shoul be used to get a single object (".count($list)." found)" );
}else if( count( $list ) == 0 ){
$classname = get_class( $obj );
return new $classname();
}
return $list[ 0 ];
}
final public function getList( $arg1 = null, $arg2 = null ){
if( is_string( $arg1 ) && class_exists( $arg1, true ) ){
$obj = new $arg1();
$params = $arg2;
}else{
$obj = &$this;
$params = $arg1;
}
$query = new SQLReactorQuery();
$query->from = &$obj;
$query->limit = $params[ 'limit' ];
$query->offset = $params[ 'offset' ];
$query->setFilter( $params[ 'filter' ] );
$query->eagerload = $params[ 'eagerload' ];
$query->orderBy = $params[ 'orderBy' ];
$query->direction = $params[ 'direction' ];
$_paramsCache['cache'] = $params['cache'];
$_paramsCache['lifetime'] = $params['lifetime'];
$sql = $query->parse();
if( SQLReactor::$_cache['active'] === true ){
if( $_paramsCache['cache'] !== false ){
if( SQLReactor::$_config['cache']['engine'] == 'memcache' ){
if( SQLReactor::$_config['cache']['memcache']['key_method_generator'] == 'md5' ){
$key = md5( $sql );
}else{
$key = sha1( $sql );
}
$resultCache = SQLReactor::$_cache['engine']->get( $key );
if( $resultCache === false ){
if( SQLReactor::$_config['cache']['memcache']['compress_data'] === true ){
$compressData = MEMCACHE_COMPRESSED;
}else{
$compressData = 0;
}
if( isset($_paramsCache['lifetime']) ){
$lifetime = $_paramsCache['lifetime'];
}else{
$lifetime = SQLReactor::$_config['cache']['memcache']['lifetime'];
}
$result = $obj->_connection->query( $sql );
$recordset = $obj->_getGroupedResults( $result, $query );
$list = $obj->_toObjectList( $recordset, $query );
SQLReactor::$_cache['engine']->add( $key,
$list,
$compressData,
$lifetime );
}else{
$list = $resultCache;
}
}
else
{
throw new Exception('No support for this cache engine.');
}
}else{
$result = $obj->_connection->query( $sql );
$recordset = $obj->_getGroupedResults( $result, $query );
$list = $obj->_toObjectList( $recordset, $query );
}
}else{
$result = $obj->_connection->query( $sql );
$recordset = $obj->_getGroupedResults( $result, $query );
$list = $obj->_toObjectList( $recordset, $query );
}
return $list;
}
final public function count( $arg1 = null, $arg2 = null ){
if( is_string( $arg1 ) && class_exists( $arg1, true ) ){
$obj = new $arg1();
$params = $arg2;
}else{
$obj = &$this;
$params = $arg1;
}
$query = new SQLReactorQuery();
$query->from = &$obj;
$query->limit = $params[ 'limit' ];
$query->offset = $params[ 'offset' ];
$query->setFilter( $params[ 'filter' ] );
$sql = $query->parseCount();
$result = $obj->_connection->query( $sql );
$rs = $obj->_connection->fetch( $result );
return (int)$rs['count'];
}
final protected function _getGroupedResults( $result, &$query ){
$groupedResults = array();
while( $rs = $this->_connection->fetch( $result ) ){
$line = array();
foreach( $rs as $colName => $value ){
$tmpName = explode( "__", $colName );
$tableName = $query->getEagerName( $tmpName[ 0 ] );
$columnName = $tmpName[ 1 ];
$line[ $tableName ][ $columnName ] = $value;
}
foreach( $line as $tableName => $columns ){
if( is_null( $groupedResults[ $tableName ] ) ){
$groupedResults[ $tableName ] = array();
}
if( !in_array( $line[ $tableName ], $groupedResults[ $tableName ] ) ){
$groupedResults[ $tableName ][] = $line[ $tableName ];
}
}
}
return $groupedResults;
}
final public function _toObjectList( &$recordset, &$query, $class = null, $clauseName = null, $filter = null, $unique = null, $backref = null ){
$list = array();
if( !$clauseName ){
$class = get_class( $this );
$clauseName = "";
}
if( is_null( $recordset[ $clauseName ] ) ){
$recordset[ $clauseName ] = array();
}
foreach( $recordset[ $clauseName ] as $line ){
if( is_null( SQLReactor::$_mappingHistory->$class ) ){
$obj = new $class();
}else{
$obj = clone SQLReactor::$_mappingHistory->$class;
}
if( $filter ){
$filterAttr = $filter[ 0 ];
$filterValue = $filter[ 1 ];
if( $line[ $obj->_columnNames->$filterAttr ] != $filterValue ) continue;
}
if( $backref ){
$obj->_table->columns->$backref[ 0 ]->setValueFromDB( $backref[ 1 ] );
}
$objectVars = get_object_vars( $obj->_table->columns );
foreach( $objectVars as $columnName => $column ){
if( $column instanceof ForeignKey || $column instanceof Backref ){
if( $column instanceof ForeignKey ){
$obj->_table->columns->{"{$columnName}Id"}->setValueFromDB( $line[ $obj->_columnNames->{ "{$columnName}Id" } ] );
}
$tmpClauseName = $clauseName ? "{$clauseName}->{$columnName}" : $columnName;
if( $query->sqlStructure->eager[ $tmpClauseName ] ){
$targetClass = $obj->_table->columns->$columnName->config[ 'target' ][ 0 ];
$targetAttr = $obj->_table->columns->$columnName->config[ 'target' ][ 1 ];
if( $column instanceof ForeignKey ){
$targetFilter = array( $targetAttr, $line[ $obj->_columnNames->{"{$columnName}Id"} ] );
$tmpUnique = true;
$tmpBackref = null;
}else{
if( is_null( SQLReactor::$_mappingHistory->$targetClass ) ){
$target = new $targetClass();
}else{
$target = clone SQLReactor::$_mappingHistory->$targetClass;
}
$backrefArg = $target->_table->columns->$targetAttr->config[ 'target' ][ 1 ];
$targetFilter = array( "{$targetAttr}Id", $line[ $obj->_columnNames->$backrefArg ] );
$tmpBackref = array( $targetAttr, &$obj );
$tmpUnique = $obj->_table->columns->$columnName->config[ 'unique' ];
}
$obj->_table->columns->$columnName->setValueFromDB( $this->_toObjectList( $recordset, $query, $targetClass, $tmpClauseName, $targetFilter, $tmpUnique, $tmpBackref ) );
}else{
if( $column instanceof ForeignKey ){
$obj->_table->columns->$columnName->lazyValue = $line[ $obj->_columnNames->{"{$columnName}Id"} ];
}else{
$targetClass2 = $obj->_table->columns->$columnName->config[ 'target' ][ 0 ];
$targetAttr2 = $obj->_table->columns->$columnName->config[ 'target' ][ 1 ];
if( is_null( SQLReactor::$_mappingHistory->$targetClass2 ) ){
$target2 = new $targetClass2();
}else{
$target2 = clone SQLReactor::$_mappingHistory->$targetClass2;
}
$backrefArg2 = $target2->_table->columns->$targetAttr2->config[ 'target' ][ 1 ];
$obj->_table->columns->$columnName->lazyValue = $line[ $obj->_columnNames->$backrefArg2 ];
}
}
}else{
$obj->_table->columns->$columnName->setValueFromDB( $line[ $obj->_columnNames->$columnName ] );
}
}
$obj->_properties[ 'exists' ] = true;
$list[] = $obj;
}
if( $unique ){
return $list[ 0 ];
}
return $list;
}
final public function id( $config = null ){
$this->_properties[ 'idCol' ] = $this->SerialCol( $config );
$this->primaryKey( 'id' );
}
final public function unique(){
$this->_unique[] = func_get_args();
}
final public function index(){
$this->_indexes[] = func_get_args();
}
final public function primaryKey(){
$this->_primaryKey = func_get_args();
}
final public function IntCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function FloatCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function StringCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function DatetimeCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function DateCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function TimeCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function BoolCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function Backref( $config = null ){
if( !is_array( $config['target'] ) ){
$config['target'] = array( $config['target'], 'id' );
}
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function ForeignKey( $config = null ){
if( !is_array( $config['target'] ) ){
$config['target'] = array( $config['target'], 'id' );
}
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function SerialCol( $config = null ){
return array(
'type' => __FUNCTION__,
'config' => $config,
);
}
final public function createTable( $arg1 ){
if( is_string( $arg1 ) && class_exists( $arg1, true ) ){
$obj = new $arg1();
}else{
$obj = &$this;
}
return $obj->_table->createTable();
}
final public function createTables( $classes = null ){
if( !$classes ){
$classes = array();
foreach( get_declared_classes() as $class ){
if( is_subclass_of( $class, SQLReactor::$_baseClass ) ){
$classes[] = $class;
}
}
}
$depTree = new SQLReactorDepTree( $classes );
$classes = $depTree->getCreationOrder();
foreach( $classes as $class ){
SQLReactor::createTable( $class );
}
}
final public function attributes(){
$attributes = array_values( get_object_vars( $this->_attributeNames ) );
return $attributes;
}
final public function toArray(){
$array = array();
foreach( $this->_table->columns as $columnName => $column ){
if( $column instanceof ForeignKey || $column instanceof Backref ) continue;
$array[ $columnName ] = $this->$columnName;
}
return $array;
}
final public function fromArray( $array ){
foreach( $this->_table->columns as $columnName => $column ){
if( $column instanceof ForeignKey || $column instanceof Backref ) continue;
if( array_key_exists( $columnName, $array ) ){
$this->$columnName = $array[ $columnName ];
}
}
}
public function validate(){
return true;
}
final public function save(){
if( $this->validate() === false ){
$className = get_class( $this );
throw new Exception( "Validator for class \"{$className}\" returned false;" );
}
return $this->_table->save();
}
final public function delete(){
return $this->_table->delete();
}
}
SQLReactor::init();
?>