Location: PHPKode > scripts > SQLReactor > SQLReactor-0.6/SQLReactor.php
<?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();

?>
Return current item: SQLReactor