Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StackOverflowError #3

Open
OndraZizka opened this issue Jun 7, 2016 · 1 comment
Open

StackOverflowError #3

OndraZizka opened this issue Jun 7, 2016 · 1 comment

Comments

@OndraZizka
Copy link
Owner

01:28:57,213 INFO [stdout](ServerService Thread Pool -- 76) INFO: Parsing programovani/php/php-object-persistence-hibernate-doctrine-orm.texy 01:28:57,249 ERROR [stderr](ServerService Thread Pool -- 76) ERROR when parsing programovani/php/php-object-persistence-hibernate-doctrine-orm.texy: java.lang.StackOverflowError

@OndraZizka
Copy link
Owner Author


PHP Object Persistence
**********************

"Documentation":lib-docs/index.html (together with my other libs)


/--code php .[brush:]
<?php



// --- INCLUDES --- //
    require_once dirname(__FILE__).'/'.'inc.php4';
    require_once dirname(__FILE__).'/'.'lib.cDBAccess_MySQL.php4';
    // Event Logging 
    require_once dirname(__FILE__).'/'.'lib.cApp.php';
    require_once dirname(__FILE__).'/'.'lib.cLog.php';
    require_once dirname(__FILE__).'/'.'lib.cObjectPersistence_ColumnTypes.php';



// --- CONSTANTS --- //

    // Column types 
    define('DWOP_TYPE_STR',  1);
    define('DWOP_TYPE_NUM',  2);
    define('DWOP_TYPE_REAL', 3);
    define('DWOP_TYPE_BOOL', 4);
    define('DWOP_TYPE_ENUM', 5);
    define('DWOP_TYPE_SET',  6);

    // Column flags 
    define('DWOP_NOT_NULL', 1);
    define('DWOP_BINARY',   2);
    //define('DWOP_BINARY',   2);

    // For cDwOpClass::GetObjectMultiIdString() 
    define('DWOP_VALUE_DUMP_MODE__SQL', 0);
    define('DWOP_VALUE_DUMP_MODE__SQL_SHORT', 2);
    define('DWOP_VALUE_DUMP_MODE__SQL_VALUES', 3);
    define('DWOP_VALUE_DUMP_MODE__ESCAPED_VALUES', 4);
    define('DWOP_VALUE_DUMP_MODE__COMA', 5);


/** *********************************************************************************
 <h1>  RDBMS Object Loader for PHP  </h1> 

    @version 2.4.6
    @author Ondra Zizka, Dynawest;  [email protected] 

    <h2>Plan:</h2> 
    3.0.0:
        - Add:      Complete cross-class references handling 
    2.x.x:
        - Class::RemProperty() - handle removal of ID and KEY properties.
            Class::aoIdProperties - rewrite so that GetIdProperties etc will compute from properties themselves. ??? Good idea???

    <h2>History:</h2>
    2.4.7:
        - Fixed:  cDwOpProperty::FormatForSql() returns correctly for DWOP_TYPE_REAL column types.
    2.4.7:
        - Added:  DeleteObject() supports multi-property PRIMARY KEYS in it's second parameter 
    2.4.6:
        - Added:  Object Pool supports multi-property PRIMARY KEYS 
    2.4.5:
        - Fixed:  Fix in SaveObject() - now saves all properties that are set. Null values for NOT NULL columns converted to default.
        - Added:  function GetNonKeyProperties()
        - Added:  DeleteObject() now can take Class name and single ID as parameters.
        - Added:  cDwOpProperty now remembers it's PRIMARY KEY properties added through AddIdProperty() etc.
        - Added:  cDwOpProperty::IsKey()
        - Changed:  cDwOpColumnType::GetDefaultDefault() is not abstract returns a value according to basic column type.
        - Fixed:  DwFwIdAndProperties::HasProperty() now uses array_key_exists() instead of fucking isset().
    2.4.4:
        - Fixed:  Massive fix in SaveObject() and one in DeleteObject()
        - Added:  param $sGlue to  GetIdPropertiesSql($oObject, $sGlue=', ')
    2.4.3:
        - Added:  Support for multi-property PRIMARY KEYs  in SaveObject(), DeleteObject()
    2.4.2:
        - Added:  DeleteObject($oObject);
        - Added:  GetIdPropertiesSql()
    2.4.1:
        - Added:  param $bOverwrite to function AddObjectIntoPool( $oObject, $bOverwrite=true )
    2.4.0:
        - Added:    M : N cross-class references 
          - Added:   function SetMnRelation($oClass1, $oClassGlue, $oClass2)
          - Added:   function LoadObjectsByMnRelation($oObject, $oClassGlue, $oClass2, $xId=null)

        - Added:   Support for multi-property PRIMARY key - but only for keeping it, not for loading or saving 

        - Added:   Real column types 
                      (abstract class cDwOpColumnType_Real extends cDwOpColumnType) and derived classes)
        - Added:   Ordinal column types can take possible values definiton as they go from the DB:
                      "'value', 'value', ..."
        - Added:   Column types BIGINT and MEDIUMINT

        Objects loading: 
        - Added:   function LoadObjects($oClass)
        - Added:   function LoadObjectsBySql($xClass, $sSQL)
        - Added:   function LoadObjectsFromResult($xClass, $oRes)

        Class related: 
      - Added:   function ConvertAndCheckClass( $oClass )
        - Added:   function CreateClassCopy( $sName, $xClass )
        - Added:   function CreateClassByTable( $sName, $sTable )
        - Added:   function CreateColumnTypeObject($sTypeString, $bNull, $sDefault)


    2.3.0:
        - Added:    CreateClassByTable()
                    Creates class definition according to a table in a database.
    2.2.1:
        - Added:    LoadObjectsFromResult().
        - Changed:  LoadObjectsByValue() now uses LoadObjectsFromResult().
    2.2.0:
        - Added:    Ordering by properties support.
                    GetOrderProperties(), SetOrderProperties()
    2.1.0:
        - Added:    Default values, better checking.
                    GetDefault(), SetDefault(), GetDefaultDefault()
    2.0.2:
        - Added:    Load* functions' first parameter can be also a class name string.
    2.0.1:
        - Added:    GetPoolCount()
        - Changed:  SaveObject() adds UPDATEd (overwrite) and INSERTed objects into pool
    2.0.0:
        - Add central object registry - object is loaded only once,
          then return the formerly loaded 
    1.1.0:
        - Added support for AUTO_INCREMENT when creating objects.
        - Added possibility to load objects by values.
 ***********************************************************************************/
class cDwObjectPersistence_DB {

    var $oDB = null;
    function &GetDB()    { return $this->oDB; }

    var $sError = null;
    function GetError()       { return $this->sError; }
    function SetError($sError){ $this->sError = $sError; }


    function cDwObjectPersistence_DB($oDB){
        $this->oDB = $oDB;
    }


    /**<***********************************************************************>
    *  Class objects dictionary                                                *
    ***************************************************************************/
    var $aoClassesDictionary = Array();
    function GetClassNames(){ return array_keys($this->aoClassesDictionary); }
    function AddClassIntoDictionary( $oClass ){
        $this->aoClassesDictionary[$oClass->GetName()] = $oClass;
    }
    function GetClassFromDictionary( $sClassName ){
        return isset($this->aoClassesDictionary[$sClassName]) ? $this->aoClassesDictionary[$sClassName] : null;
    }


    /**<***********************************************************************>
    *  Objects pool                                                            *
    ***************************************************************************/
    var $aaoObjectPool = Array();

    function AddObjectIntoPool( $oObject, $bOverwrite=true ){
        if( !is_object($oObject) ) return false;
        $sClassName = get_class($oObject);
        //App::Log("\$sClassName: ".$sClassName);///
        if( !$oClass = $this->GetClassFromDictionary($sClassName) ){ return false; }

        // Get the object unique PRIMARY KEYs string 
        //App::Log("GetIdPropertiesCount(): ".$oClass->GetIdPropertiesCount());///
        switch( $oClass->GetIdPropertiesCount() ){
            case 0: $bUsePool = false; return false; break;
            case 1:  $xId = $oObject->GetId(); break;
            default: $xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES ); break;
        }
        //if( 0 == $oClass->GetIdPropertiesCount() ) return false;
        //$xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES );

        // If we should not overwrite... 
        if( !$bOverwrite ){
            // and the object exists, don't write and return true. 
            if( isset( $this->aaoObjectPool[get_class($oObject)][$xId] ) )
                return true;
        }

        // Save the object handle. 
        $this->aaoObjectPool[get_class($oObject)][$xId] = $oObject;
        return true;
    }

    function AddObjectIntoPool_SingleId( $oObject, $bOverwrite=true ){
        if( !is_object($oObject) ) return false;

        // If we should not overwrite... 
        if( !$bOverwrite ){
            // and the object exists, don't write and return true. 
            if( isset( $this->aaoObjectPool[get_class($oObject)][$oObject->GetId()] ) )
                return true;
        }

        // Save the object handle. 
        $this->aaoObjectPool[get_class($oObject)][$oObject->GetId()] = $oObject;
        return true;
    }

    function GetObjectFromPool( $sClassName, $xId ){
        $oObject = null;
        if( isset($this->aaoObjectPool[$sClassName][$xId]) ){
            $oObject = $this->aaoObjectPool[$sClassName][$xId];
            //echo "<pre><strong>Pool hit!</strong></pre>";///
        }
        return $oObject;
    }


    function RemObjectFromPool( $oObject ){
        if( !is_object($oObject) ) return false;
        // Get the Class object 
        $sClassName = get_class($oObject);
        if( !($oClass = $this->GetClassFromDictionary($sClassName)) ){ return false; }

        // Get the object unique PRIMARY KEYs string 
        //App::Log("GetIdPropertiesCount(): ".$oClass->GetIdPropertiesCount());///
        switch( $oClass->GetIdPropertiesCount() ){
            case 0: $bUsePool = false; return false; break;
            case 1:  $xId = $oObject->GetId(); break;
            default: $xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES ); break;
        }
        // Remove the object with the acquired ID 
        if( isset( $this->aaoObjectPool[$sClassName][$xId] ) )
            unset($this->aaoObjectPool[$sClassName][$xId]);
        return true;
    }       

    function RemObjectFromPool_SingleId( $oObject ){
        if( !is_object($oObject) ) return false;
        $sClassName = get_class($oObject);
        $xId = $oObject->GetId();
        if( isset( $this->aaoObjectPool[$sClassName][$xId] ) )
            unset($this->aaoObjectPool[$sClassName][$xId]);
        return true;
    }


    function HasObjectInPool( $oObject ){
        if( !is_object($oObject) ) return false;
        // Get the Class object 
        $sClassName = get_class($oObject);
        if( !($oClass = $this->GetClassFromDictionary($sClassName)) ){ return false; }

        // Get the object unique PRIMARY KEYs string 
        switch( $oClass->GetIdPropertiesCount() ){
            case 0: $bUsePool = false; break;
            case 1:  $xId = $oObject->GetId(); break;
            default: $xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES ); break;
        }
        // $xId = $oObject->GetId();

        $xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES );

        if( isset( $this->aaoObjectPool[get_class($oObject)][$xId] ) )
            return true;
        return false;
    }

    function HasObjectInPool_SingleId( $oObject ){
        if( !is_object($oObject) ) return false;
        $xId = $oObject->GetId();
        if( isset( $this->aaoObjectPool[get_class($oObject)][$xId] ) )
            return true;
        return false;
    }


    function GetPoolCount(){
        return array_sum(array_map('Count', $this->aaoObjectPool));
    }






    /**<***********************************************************************>
    *  Converts the class name to the DwOpClass object and checks the class.   *
    ***************************************************************************/
    function ConvertAndCheckClass( $oClass ){

        // $oClass can be either cDwOpClass object or a class name 
        if( !$oClass ){ $this->SetError("Bad param (null) \$oClass for ".__METHOD__." in ".CallInfo(-2)); return false; }

        // A string - try to find the Class in the Dictionary 
        if( is_string($oClass) ){
            $sClassName = $oClass;
            $oClass = $this->GetClassFromDictionary($sClassName);
            if( !is_object($oClass) ){ $this->SetError("Class [$sClassName] not found in dictionary."); return false; }
        }
        // Not a string - either the Class object or an object of some Class. 
        else{
            if( !is_object($oClass) ){ $this->SetError("Bad param (not string or object) \$oClass for ".__METHOD__." in ".CallInfo(-2)); return false; }

            if( $oClass instanceof cDwOpClass ){
                $sClassName = $oClass->GetName();
            }else{
                // object of some Class - try to find the Class object.
                $sClassName = get_class($oClass);
                $oClass = $this->GetClassFromDictionary( $sClassName );
                if( !is_object($oClass) ){ $this->SetError("Object's Class [$sClassName] not found in dictionary."); return false; }
            }
        }

        // Check class existence 
        if( !class_exists($sClassName) ){ $this->SetError("Non-existent PHP class [$sClassName]. in ".__METHOD__." in ".CallInfo(-2)); return false; }

        return $oClass;
    }


    /**<***********************************************************************>
    *  Deletes an object with ID $iID  of the given class $oClass              *
       Deletes the given object from the DB. 
    ***************************************************************************/
    function DeleteObject($oObject, $xId=null){
        //echo "<pre>\n class object: ".AdjustedPrintR($oClass)."</pre>";///
        //if( !is_object( $oObject ) ) return false;
        $bSucc = false;
        do{

            /*// Find the Class object in the Dictionary 
            $sClassName = get_class($oObject);
            do{
                $oClass = $this->GetClassFromDictionary($sClassName);
                $sClassName = get_parent_class($sClassName);
            }while( $sClassName && !$oClass );

            // Check class object //
            //if( !$oClass ){ $this->SetError("Class [$sClassName] not found in the dictionary in ".__METHOD__." @ ".__LINE__); break; }
            /*/
            if( !($oClass = $this->ConvertAndCheckClass($oObject)) ){ $this->SetError("!\$oClass in ".__METHOD__." @ ".__LINE__); break; }
            $sClassName = $oClass->GetName();
            /**/

            // If $oObject is an instance of Class, get it's ID.

            // If $oObject was just identification of a Class, create a temporary object with just IDs.
            if( !($oObject instanceof $sClassName) ){
                // Check the ID - false, null, or empty array? -> fail.
                if( !$xId ){ $this->SetError("Unknown object ID to delete in ".__METHOD__." @ ".__LINE__); break; }

                // If $xId is multiple ID - associative array of ids 
                if( is_array($xId) ){
                    $oObject = new $sClassName();
                    foreach( $xId as $k => $v )
                        $oObject->SetProperty($k, $v);
                }
                // Single ID 
                else{
                    //$oObject = $this->LoadObjectById($oClass, $xId);
                    //if(!$oObject) { $this->SetError("!LoadObjectById(".$oClass->GetName().", $xId) in ".__METHOD__." @ ".__LINE__); break; }
                    $oObject = new $sClassName();
                    $oObject->SetId($xId);
                }
            }

            // -- From here further, we have DwOpClass $oClass  and  it's (pseudo)instance $oObject. -- //


            // Remove from the Object Pool 
            if( !$this->RemObjectFromPool($oObject) ){ $this->SetError("!RemObjectFromPool() in ".__METHOD__." @ ".__LINE__); }


            $sTable  = $oClass->GetTable();

            /*/ For one ID (PRIMARY KEY) property only...
            $oIdProp = $oClass->GetIdProperty();
            $sColId  = $oIdProp->GetColName();
            $xId = $oIdProp->FormatForSql( $oObject->GetProperty($oIdProp->GetColName()) );
            $sSQL = "DELETE FROM $sTable WHERE $sColId = $xId";
            /*/
            // For all PRIMARY KEYs: 
            $saxIdsCond = $oClass->GetIdPropertiesSql($oObject, ' AND ');
            $sSQL = "DELETE FROM $sTable WHERE $saxIdsCond";
            /**/

            // Perform SQL query 
            $oRes = $this->GetDB()->Execute($sSQL);
            if( !$oRes || !$oRes->IsOK() ){ $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]"); break; }

            $bSucc = true;
        }while(false);
        return $bSucc;
    }// cDwObjectPersistence_DB::DeleteObject() 


    /**<*********************************************************************************>
    *  Deletes objects of the given class $oClass   
    *  with some property equal to the given value.  

      @param oClass:         cDwOpClass object or a class name to find in dictionary.
      @param sPropertyName:  Name of the property to compare with $xVal.  
      @param xVal:           Value to compare property with.              
        @returns array of objects with value $sPropertyName equal to $xVal. 
    *************************************************************************************/
    function DeleteObjectsByValueBad($oClass, $sPropertyName, $xVal){
        //echo "<pre>\n class object: ".AdjustedPrintR($oClass)."</pre>";///

        if( !( $oClass = $this->ConvertAndCheckClass($oClass) ) ) return false;
        $sClassName = $oClass->GetName();


        // Get the property by which to recognize the object to load 
        $oProp = $oClass->GetProperty($sPropertyName);
        if( null == $oProp ){ $this->SetError("No such property in class [$sClassName]: [$sPropertyName]"); return false; }

        // Create SQL query 
        $sTable  = $oClass->GetTable();
        $sCol    = $oProp->GetColName();
        $oIdProp = $oClass->GetIdProperty();
        $sColId  = $oIdProp->GetColName();

        $sOrderSql = $oClass->GetOrderPropertiesSql();   // ORDER BY part 
        $sSQL = "DELETE FROM $sTable WHERE $sCol = ".$oProp->FormatForSql($xVal)." ".$sOrderSql;

        // Perform SQL query 
        $oRes = $this->GetDB()->Execute($sSQL);
        if( !$oRes || !$oRes->IsOK() ){ $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL] in ".__METHOD__.' @ '.__LINE__); return false; }

        // TODO: Remove objects from Object Pool!
        $this->RemObjectFromPool($oObject);

        return true;
    }// LoadObjectsByValue($oClass, $sPropertyName, $xVal)


    /**<***********************************************************************>
    *  Second version                                                          *
    ***************************************************************************/
    function DeleteObjectsByValue($oClass, $sPropertyName, $xVal){

        $aoObjects = $this->LoadObjectsByValue($oClass, $sPropertyName, $xVal);
        if(!is_array($aoObjects)) return false;

        foreach( $aoObjects as $oObject){
            $this->DeleteObject($oObject);
        }

        return true;
    }// LoadObjectsByValue($oClass, $sPropertyName, $xVal)




    /**<***********************************************************************>
    *  Loads an object with ID $iID  of the given class $oClass                *
    ***************************************************************************/
    function LoadObjectById($oClass, $iID){
        //echo "<pre>\n class object: ".AdjustedPrintR($oClass)."</pre>";///


        /*/ First parameter can be either cDwOpClass object or a class name 
        if( is_string($oClass) ){
            $sClassName = $oClass;
            $oClass = $this->GetClassFromDictionary($sClassName);
            if( !$oClass ) return false;
        }else{
            $sClassName = $oClass->GetName();
        }
        // Check class existence 
        if( !class_exists($sClassName) )
            return false;
        /*/
        if( !( $oClass = $this->ConvertAndCheckClass( $oClass ) ) ) return false;
        $sClassName = $oClass->GetName();/**/


        // Look for the object in the pool 
        if( $oObject = $this->GetObjectFromPool($sClassName, $iID) )
            return $oObject;



        $sTable  = $oClass->GetTable();
        $oIdProp = $oClass->GetIdProperty();
        $sColId  = $oIdProp->GetColName();

        // Create and perform SQL query 
        $sSQL = "SELECT * FROM $sTable WHERE $sColId = ".$oIdProp->FormatForSql($iID);
        $oRet = $this->GetDB()->Select($sSQL);
        if( !$oRet->IsOK() ) return false;
        if( 0 == $oRet->NumRows() ) return null;

        $aData = $oRet->FetchRow(MYSQL_ASSOC);

        // Create the object 
        $oObject = new $sClassName();

        // Load all declared properties 
        foreach( $oClass->GetProperties() as $oProperty ){
            //echo "<div>\$oObject->SetProperty( ".$oProperty->GetColName().", \$aData[".$oProperty->GetColName()."] );</div>";///
            $oObject->SetProperty( $oProperty->GetColName(), $oProperty->GetColType()->ConvertFromResult( $aData[$oProperty->GetColName()] ) );
        }

        $this->AddObjectIntoPool($oObject);
        return $oObject;
    }// LoadObjectById($oClass, $iID) 





    /**<***********************************************************************************>
    *  Loads all objects of the given class $oClass.                                        

      @param oClass:         cDwOpClass object or a class name to find in dictionary.       
        @returns array of objects of the specified class.                                     
    ***************************************************************************************/
    function LoadObjects($oClass){

        /*/ First parameter can be either cDwOpClass object or a class name 
        if( is_string($oClass) ){
            $sClassName = $oClass;
            $oClass = $this->GetClassFromDictionary($sClassName);
            if( !is_object($oClass) ){ $this->SetError("Class [$sClassName] not found in dictionary."); return false; }
        }else{
            $sClassName = $oClass->GetName();
        }

        // Check class existence 
        if( !class_exists($sClassName) ){ $this->SetError("Non-existent class [$sClassName]."); return false; }
        /*/
        if( !( $oClass = $this->ConvertAndCheckClass( $oClass ) ) ) return false;
        $sClassName = $oClass->GetName();/**/


        // Create SQL query 
        $sTable    = $oClass->GetTable();
        $sOrderSql = $oClass->GetOrderPropertiesSql();   // ORDER BY part 
        $sSQL = "SELECT * FROM $sTable ".$sOrderSql;

        // Perform SQL query 
        $oRes = $this->GetDB()->Select($sSQL);
        if( !$oRes || !$oRes->IsOK() ){
            $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]");
            return false;
        }

        // Load Objects 
        $aoObjects = $this->LoadObjectsFromResult($oClass, $oRes);
        $oRes->FreeResult();

        return $aoObjects;

    }// cDwObjectPersistence_DB::LoadObjects()



    /**<*********************************************************************************>
    *  Loads an objects of the given class $oClass   
    *  with some property equal to the given value.  

      @param oClass:         cDwOpClass object or a class name to find in dictionary.
      @param sPropertyName:  Name of the property to compare with $xVal.  
      @param xVal:           Value to compare property with.              
        @returns array of objects with value $sPropertyName equal to $xVal. 
    *************************************************************************************/
    function LoadObjectsByValue($oClass, $sPropertyName, $xVal){
        //echo "<pre>\n class object: ".AdjustedPrintR($oClass)."</pre>";///


        /*/ First parameter can be either cDwOpClass object or a class name 
        if( is_string($oClass) ){
            $sClassName = $oClass;
            $oClass = $this->GetClassFromDictionary($sClassName);
            if( !is_object($oClass) ){
                $this->SetError("Class [$sClassName] not found in dictionary.");
                return false;
            }
        }else{
            $sClassName = $oClass->GetName();
        }

        // Check class existence 
        if( !class_exists($sClassName) ){ $this->SetError("Non-existent class [$sClassName]."); return false; }
        /*/
        if( !( $oClass = $this->ConvertAndCheckClass( $oClass ) ) ) return false;
        $sClassName = $oClass->GetName();/**/


        // Get the property by which to recognize the object to load 
        $oProp = $oClass->GetProperty($sPropertyName);
        if( null == $oProp ){
            $this->SetError("No such property in class [$sClassName]: [$sPropertyName]");
            return false;
        }

        // Create SQL query 
        $sTable  = $oClass->GetTable();
        $sCol    = $oProp->GetColName();
        $oIdProp = $oClass->GetIdProperty();
        $sColId  = $oIdProp->GetColName();

        $sOrderSql = $oClass->GetOrderPropertiesSql();   // ORDER BY part 
        $sSQL = "SELECT * FROM $sTable WHERE $sCol = ".$oProp->FormatForSql($xVal)." ".$sOrderSql;

        /*// Perform SQL query 
        $oRes = $this->GetDB()->Select($sSQL);
        if( !$oRes || !$oRes->IsOK() ){
            $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]");
            return false;
        }

        // Load Objects 
        $aoObjects = $this->LoadObjectsFromResult($oClass, $oRes);
        $oRes->FreeResult();/*/

        $aoObjects = $this->LoadObjectsBySql($oClass, $sSQL);/**/

        return $aoObjects;
    }// LoadObjectsByValue($oClass, $sPropertyName, $xVal)





    /**<*********************************************************************************>
    *  Performs SQL query and converts the result to an array of objects.                *
    *************************************************************************************/
    function LoadObjectsBySql($xClass, $sSQL){

        // Perform SQL query 
        $oRes = $this->GetDB()->Select($sSQL);
        if( !$oRes || !$oRes->IsOK() ){
            $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]");
            return false;
        }

        // Load Objects 
        $aoObjects = $this->LoadObjectsFromResult($xClass, $oRes);
        $oRes->FreeResult();

        return $aoObjects;
    }



    /**<*********************************************************************************>
    *  Converts the result to an array of objects.                                       *
    *************************************************************************************/
    function LoadObjectsFromResult($xClass, $oRes){

        // Convert and check the class 
        if( !( $oClass = $this->ConvertAndCheckClass( $xClass ) ) ){
            $this->SetError("!ConvertAndCheckClass(".gettype($xClass)."): ".$this->GetError()); return false;
        }
        $sClassName = $oClass->GetName();


        $aoObjects = Array();
        //if( 0 == $oRes->NumRows() ) return $aoObjects;

        // Class Name 
        //if( !is_object($oClass) ) echo CallInfo(-2);



        $aoPropertiesToTraverse = Array();
        // Get the list of the properties that will be set from the result. 
        // Check column existence for all declared properties 
        $aoFields = $oRes->GetColumns();
        $absFieldsContained = Array();
        foreach( $aoFields as $oField ){ $absFieldsContained[$oField->name] = 1; }
        //App::Log("\$absFieldsContained: ".AdjustedPrintR($absFieldsContained));///
        foreach( $oClass->GetProperties() as $oProperty ){
            $sColName = $oProperty->GetColName();
            if( !array_key_exists($sColName, $absFieldsContained) ){
                $this->SetError("Warn: Column [$sColName] for property [".$oProperty->GetName()."] is not set; in ".__METHOD__." in ".CallInfo(-2));
                App::Log("Warn: Column [$sColName] for property [".$oProperty->GetName()."] is not set; in ".__METHOD__); continue;
            }
            $aoPropertiesToTraverse[] = $oProperty;
        }/**/

        // Create the objects. For each row: 
        while( $aData = $oRes->FetchRow(MYSQL_ASSOC) ){
            $oObject = new $sClassName();
            /*// Load all declared properties 
            foreach( $oClass->GetProperties() as $oProperty ){
                //echo "<div>\$oObject->SetProperty( ".$oProperty->GetColName().", \$aData[".$oProperty->GetColName()."] );</div>";///
                $sColName = $oProperty->GetColName();
                //if( !isset( $aData[$sColName] ) && !is_null($aData[$sColName]) ){
                if( !array_key_exists($sColName, $aData) ){             
                    App::Log("Warn: Column [$sColName] for property [".$oProperty->GetName()."] is not set."); continue;
                } // DONE: Move before the while - fetch_fields()
                $oObject->SetProperty( $oProperty->GetColName(), $aData[$sColName] );
            }/**/
            // Load properties that are present in the result 
            foreach( $aoPropertiesToTraverse as $oProperty ){
                //echo "<div>\$oObject->SetProperty( ".$oProperty->GetColName().", \$aData[".$oProperty->GetColName()."] );</div>";///
                $sColName = $oProperty->GetColName();
                $oObject->SetProperty( $sColName, $oProperty->GetColType()->ConvertFromResult($aData[$sColName]) );
            }

            // Get object's unique ID, if possible 
            $bUsePool = true;
            //App::Log("GetIdPropertiesCount(): ".$oClass->GetIdPropertiesCount());///
            switch( $oClass->GetIdPropertiesCount() ){
                case 0: $bUsePool = false; break;
                case 1:  $xId = $oObject->GetId(); break;
                default: $xId = $oClass->GetObjectMultiIdString( $oObject, DWOP_VALUE_DUMP_MODE__SQL_VALUES ); break;
            }

            // Look for the object in the pool 
            if(!$bUsePool)
                $aoObjects[] = $oObject;
            else
            if( $oObjectFromPool = $this->GetObjectFromPool($sClassName, $xId) ){
                //App::Log("Pool hit [$sClassName, ".$xId."]");///
                $aoObjects[] = $oObjectFromPool;
            }else{
                //App::Log("Pool miss [$sClassName, ".$xId."]");///
                $aoObjects[] = $oObject;
                $this->AddObjectIntoPool($oObject);
            }

        }// while( for each row in result )

        return $aoObjects;

    }// cDwObjectPersistence::LoadObjectsFromResult($oClass, $oRes)




    /**<***********************************************************************>
    *  Saves an object with ID $iID  of the given class $oClass                *
    ***************************************************************************/
    function SaveObject($oObject){
        //echo "<pre>\n class object: ".AdjustedPrintR($oClass)."</pre>";///
        //App::Log("Object: ".AdjustedPrintR($oObject));///
        if( !is_object( $oObject ) ) return false;


        // Whether to try UPDATE first before INSERT 
        $bDoUpdate = true;
        $bZeroUpdatedRows = false;


        // Add this Class object into the dictionary 

        // Get the Class object - for this PHP class or some ancestor class 
        $sClassName = get_class($oObject);
        do{
            $oClass = $this->GetClassFromDictionary($sClassName);
            $sClassName = get_parent_class($sClassName);
        }while( $sClassName && !$oClass );

        // Check class object //
        if( !$oClass )
            return false;

        $sTable  = $oClass->GetTable();



        /// --- PRIMARY KEYS --- ///


        /*/ For one ID (PRIMARY KEY) property only...
        $oIdProp = $oClass->GetIdProperty();         //x  Single-ID way
        $sColId  = $oIdProp->GetColName();           //x  Single-ID way
        $iID = $oObject->GetProperty( $oIdProp->GetColName() );
        $sIdForSql = $oIdProp->FormatForSql($iID); //x  Single-ID way
        /*/
        // For all PRIMARY KEYs: 
        $saxIds = $oClass->GetIdPropertiesSql( $oObject );
        $saxIdsCond = $oClass->GetIdPropertiesSql( $oObject, ' AND ' );
        /**/





        /// --- VALUES --- ///


        // Store all declared properties - create the SQL part 
        $asSqlParts = Array();
        $aoIdProps = $oClass->GetIdProperties();
        foreach( $oClass->GetProperties() as $oProperty ){
            //echo "<div>\$oObject->GetProperty( ".$oProperty->GetColName()." );</div>";///
            $bPropertySet = $oObject->HasProperty( $oProperty->GetName() );
            //App::Log("HasProperty(".$oProperty->GetName().") ? : ".(int)$oObject->HasProperty($oProperty->GetName()) );///


            // Check whether PRIMARY KEY properties are set and skip them.
            //if( $oProperty == $oIdProp ) continue;
            //if( $oProperty->IsIdProperty() ) continue;
            if( in_array($oProperty, $aoIdProps) ){
                // Multi-property PRIMARY key and some is not set -> error 
                if( !$bPropertySet  &&  1 < Count($aoIdProps) ){
                    $this->SetError("Multi-property PRIMARY - property [".$oProperty->GetName()."] not set! in ".__METHOD__." @ ".__LINE__);
                    //App::Log("Error!");///
                    return false;
                }
                // Skip ID column(s) - we don't want to set them as other properties 
                //App::Log("Skipping ID col [".$oProperty->GetName()."]");///
                continue;
            }

            // Skip undefined properties - or set them to DEFAULT ???  ->  TODO 
            if( !$bPropertySet ){
                //$xVal = $oProperty->GetDefault();
                continue;
            }

            //App::Log("Adding col [".$oProperty->GetName()."]");///
            $sPropColName = $oProperty->GetColName();
            $xVal = $oObject->GetProperty( $sPropColName );
            //if( $sPropColName == 'targeting_fee' ) App::Log("FDG: (".gettype($xVal).") $xVal, ".AdjustedPrintR($oProperty));///
            if( null === $xVal && !$oProperty->GetColType()->IsNull() )
                $xVal = $oProperty->GetColType()->GetDefault();
            $asSqlParts[] = $sPropColName.'='.$oProperty->FormatForSql($xVal);
        }
        $saParts = implode(', ', $asSqlParts);
        //App::Log("\$saParts: ".$saParts);///

        // Multiple KEY and nothing set - error (we do not INSERT unset multi-column rows )
        // So other way:  Don't try UPDATE if no other property set.
        if( 0 == Count($asSqlParts) &&  1 < Count($aoIdProps) ){
            //$this->SetError("Multiple KEY and no normal properties set. in ".__METHOD__." @ ".__LINE__);); return false;
            $bDoUpdate = false;
            $bZeroUpdatedRows = true;
        }




        // Whether to add the object into the Object Pool. 
        // Generally, add if the object was created; don't overwrite we are only saving existing object.
        $bAddObjectIntoPool = true;
        $bOverwriteObjectInPool = false;

        // If we are saving single-prop PRIMARY KEYed object and it is set to null, we should inser new. 
        //App::Log("ID: (".gettype($oObject->GetId()).") ".$oObject->GetId());///
        if( 1 == $oClass->GetIdPropertiesCount() && (null === $oObject->GetId()) ){
            $bDoUpdate = false;
        }

        // Multi-property PRIMARY key and some is not set -> error 
        //if( 1 < $oClass->GetIdPropertiesCount() ){ } // Done above

        // UPDATE 
        //if( null !== $oObject->GetId() )
        if( $bDoUpdate ){
            // Create SQL query 
            //$sSQL = "UPDATE $sTable SET %s WHERE $sColId = ".$sIdForSql;  //x  Single-ID way
            $sSQL = "UPDATE $sTable SET %s WHERE $saxIdsCond";
            $sSQL = sprintf($sSQL, $saParts );
            //echo "<div>$sSQL</div>";///

            // Perform SQL query 
            $oRet = $this->GetDB()->Execute($sSQL);
            //echo $oRet->GetError();///
            if( !($oRet->IsOK()) ){ $this->SetError($oRet->GetError()); return false; }
            if( 0 < $oRet->NumRows() ){
                $this->AddObjectIntoPool( $oObject );
                return true;
            }
            $bZeroUpdatedRows = true;
            /*// If commented, tries to INSERT. If not, checks here for the row existence first.
            // No rows affected - did we UPDATE with no changes, or the row was not found? 
            $sSQL = "SELECT COUNT(*) FROM $sTable WHERE $sColId = ".$sIdForSql;
            //$oRet = $this->GetDB()->Execute($sSQL);
            //if( !($oRet->IsOK()) ){ $this->SetError($oRet->GetError()); return false; }
            //if( 0 != $oRet->GetCell(0,0) ){
            $sVal = $oRet->SelectCell($sSQL, 0,0);
            if( null === $sVal ){ $this->SetError("Error in SelectCell: [$sSQL]"); return false; }
            if( 0 != $oRet->GetCell(0,0) ){
                $this->AddObjectIntoPool( $oObject );
                return true;
            }/**/
        }

        // Row with ID not found  or  object ID is NULL -> INSERT 
        do{
            //$sSQL = "INSERT INTO $sTable SET $sColId = $sIdForSql, ".$saParts;
            $sSQL = "INSERT INTO $sTable SET $saxIds ";
            if( $saParts ) $sSQL .= ", ".$saParts;
            //echo "<div>$sSQL</div>";///


            // Perform SQL query 
            //echo "\nSelectMode: ".(int)$this->GetDB()->GetSelectMode();///
            $oRet = $this->GetDB()->Execute($sSQL);

            // If we did UPDATE with no changes, 
            if($bZeroUpdatedRows){
                // And this insert caused error 1062, then it's OK - the row just existed before.
                if( !$oRet || (!$oRet->IsOK() && $oRet->GetErrno() == 1062 ) ){
                    break;
                }
            }
            /*/ Other way:
            // If INSERT caused an error: 
            if( !$oRet || !$oRet->IsOK() ){
                // If we did not UPDATE with no changes  OR   the error is DUPLICATE ENTRY 
                if(!$bZeroUpdatedRows || ( !$oRet || $oRet->GetErrno() == 1062 ) )
                    $this->SetError("INSERT failed. ".($oRet ? '['.$oRet->GetError().']' : '')); return false; 
            }/* Too complex */
            if( 0 == $oRet->NumRows() ){ $this->SetError("INSERT affected no rows. SQL: [".$sSQL."] Errno: ".$oRet->GetErrno()); return false; }
            $oObject->SetId( $this->GetDB()->GetLastInsertId() );

            // We created a new object, thus if any of the same ID is in the pool, overwrite. 
            $bOverwriteObjectInPool = true;

        }while(false);

        // If asked, put object into the bool. If already there, it's overwritten. 
        if( $bAddObjectIntoPool )
            $this->AddObjectIntoPool( $oObject, $bOverwriteObjectInPool );
        return true;

    }// LoadObjectById($oClass, $iID) 





    /**<***********************************************************************>
    *  Creates a cDwOpClass object copy.                                       *
    ***************************************************************************/
    function CreateClassCopy( $sName, $xClass ){

        // The second parameter can be either cDwOpClass object or a class name 
        if( is_string($xClass) ){
            $sClassName = $xClass;
            $xClass = $this->GetClassFromDictionary($sClassName);
            if( !is_object($xClass) ){
                $this->SetError("Class [$sClassName] not found in dictionary.");
                return false;
            }
        }else{
            $sClassName = $xClass->GetName();
        }

        // Make a copy 
        $oClass = clone $xClass;
        $oClass->SetName( $sName );
        $this->AddClassIntoDictionary($oClass);
        return $oClass;
    }


    /**<***********************************************************************>
    *  Creates a cDwOpClass using it's definition from database.               *
    ***************************************************************************/
    function CreateClassByTable( $sName, $sTable ){

        do{
            $oClass = new cDwOpClass( $sName, $sTable );

            // Load columns info 
            $sSQL = "SHOW COLUMNS FROM $sTable";
            $oRes = $this->GetDB()->Select($sSQL);
            if( !$oRes || !$oRes->IsOK() ){ $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]"); return null; }

            // For each row (which represent columns in the table)
            while( $a = $oRes->FetchRow(MYSQL_ASSOC) ){
                $oColObject = $this->CreateColumnTypeObject( $a['Type'], $a['Null']=='YES', $a['Default'] );
                if( null === $oColObject ){
                    $this->SetError("Table column [$sTable.".$a['Field']."]: ".$this->GetError()." in ".__METHOD__." @ ".__LINE__);
                    //App::Log($this->GetError());///
                    $oClass = null; break;
                }
                $oProp = $oClass->AddProperty($a['Field'], $oColObject );
                $oProp->SetNull($a['Null']=='YES');

                if( 'PRI' == $a['Key'] ){ $oClass->AddIdProperty($oProp); }

                $oProp->SetUnique( 'PRI' == $a['Key']  ||  'UNI' == $a['Key'] );
            }
            $oRes->FreeResult();

            if($oClass)
                $this->AddClassIntoDictionary($oClass);

        }while(false);
        return $oClass;
    }


    //$oClass = $oOP->ExtendClassByTable('cAdplazeAdPage_Coupon', 'cAdplazeAdPage', 'ap_adpages_coupons');/**/
    /**<***********************************************************************>
    *  Extends an existings cDwOpClass with columns from a table.              *
    ***************************************************************************/
    function ExtendClassByTable($sClassNameNew, $sClassNameOld, $sTable){

        // Old Class object 
        $oClassOld = $this->ConvertAndCheckClass($sClassNameOld);
        if(!$oClassOld){ $this->SetError("!ConvertAndCheckClass($sClassNameOld)"); return null; }
        $sClassNameOld = $oClassOld->GetName();
        $aoOldIdProps = $oClassOld->GetIdProperties();

        //$oClass = $this->CreateClassCopy($sClassNameNew, $sClassNameOld);
        //$oClass = $this->CreateClassByTable($sClassNameNew, $sTable);
        $oClass = new cDwOpClass( $sClassNameNew, $sTable );
        $oClass->_SetParentClass( $oClassOld );

        // Load columns info 
        $sSQL = "SHOW COLUMNS FROM $sTable";
        $oRes = $this->GetDB()->Select($sSQL);
        if( !$oRes || !$oRes->IsOK() ){ $this->SetError("SQL error [".$oRes->GetError()."] SQL: [$sSQL]"); return null; }

        // --- Add the Properties to the Class --- //

        // Temp array to store matched id properties //
        $aoMatchedIdProps = Array();

        // For each row (which represent columns in the table)
        while( $a = $oRes->FetchRow(MYSQL_ASSOC) ){
            $oColObject = $this->CreateColumnTypeObject( $a['Type'], $a['Null']=='YES', $a['Default'] );
            if( null === $oColObject ){
                $this->SetError("Table column [$sTable.".$a['Field']."]: ".$this->GetError()." in ".__METHOD__." @ ".__LINE__);
                $oClass = null; break;
            }
            $oProp = $oClass->AddProperty($a['Field'], $oColObject );
            $oProp->SetNull($a['Null']=='YES');

            // ID property 
            if( 'PRI' == $a['Key'] ){
                // Check whether the old Class has the same ID Property 
                if( !$oClassOld->HasIdProperty($oProp->GetName()) ){
                    $this->SetError("Table column [$sTable.".$a['Field']."]: Extended Class [$sClassNameOld] doesn't have the ID column [".$oProp->GetName()."] in ".__METHOD__." @ ".__LINE__);
                    $oClass = null; break;
                }
                $oClass->AddIdProperty($oProp); // To se tam dostane z CreateClassByTable()
                // Add this old Class' Property to the list of matched ID Properties 
                $oOldClass_IdProp = $oClassOld->GetProperty($oProp->GetName());
                $aoMatchedIdProps[] = $oOldClass_IdProp;
            }

            $oProp->SetUnique( 'PRI' == $a['Key']  ||  'UNI' == $a['Key'] );
        }
        $oRes->FreeResult();

        // Check whether all ID Properties of old Class were matched 
        foreach( $oClassOld->GetIdProperties() as $oOldClass_IdProp){
            if( !in_array($oOldClass_IdProp, $aoMatchedIdProps) ){
                $this->SetError("Extended Class [$sClassNameOld] doesn't have the ID column [".$oOldClass_IdProp->GetName()."] in ".__METHOD__." @ ".__LINE__);
                $oClass = null; break;
            }
        }

        if($oClass) $this->AddClassIntoDictionary($oClass);
        return $oClass;

    }// cDwObjectPersistence::ExtendClassByTable()



    /**<**********************************************************************************>
    *  Returns cDwOpColumnType object according to the definition in $sTypeString.        *
    **************************************************************************************/
    function CreateColumnTypeObject($sTypeString, $bNull, $sDefault){
        $oRet = null;

        //$sTypeName = substr( $sTypeString, 0, strpos($sTypeString, '(') );
        //if( !ereg( '(.*)(\\((.*)\\))?(.*)?', $sTypeString, $asParts ) ) return $oRet;
        //$asParts = array_map('strtoupper', array_map('trim', $asParts) );
        //echo "<pre>\$asParts: ".AdjustedPrintR($asParts)."</pre>";///
        // list($sTypeName, $sData, $sUnsigned) = $asParts;
        $sTypeName = strtoupper(trim(strtok($sTypeString, '(') ));
        $sData     =           (trim(strtok(')') ));
        $sUnsigned = strtoupper(trim(strtok('')  ));
        //echo "<pre>$sTypeName, $sData, $sUnsigned</pre>";///

        $sClassName = 'cDwOpColumnType_';

        /*switch($sTypeName){
            case 'VARCHAR': 
            case '': $sClassName = '_'.$sTypeName;  break;
        }/**/

        $saImplementedColumnTypes = DWOP_IMPLEMENTED_COLUMN_TYPES;
        $asImplementedTypes = array_map('trim', explode(',', $saImplementedColumnTypes));
        if( !in_array( $sTypeName, $asImplementedTypes) ){ $this->SetError("Column type [$sTypeName] not implemented."); return null; }

        $sClassName .= $sTypeName;

        if( $sUnsigned == 'UNSIGNED' )
            $sClassName .= '_UNSIGNED';

        if(!class_exists($sClassName)){ $this->SetError("Undefined column class [$sClassName]."); return null; }

        switch($sTypeName){
            case 'ENUM': case 'SET':
                $oRet = new $sClassName($sData); break;
            case 'CHAR': case 'VARCHAR':
                $oRet = new $sClassName((int)$sData); break;
            default:
                $oRet = new $sClassName(); break;
        }
        if( $oRet ){
            $oRet->SetDefault($sDefault);
            $oRet->SetNull($bNull);
        }


        return $oRet;

    }// cDwObjectPersistence::CreateColumnTypeObject()


    /**<***********************************************************************>
    *   Sets M : N relation between two classes using third class as a glue.   *
    ***************************************************************************/
    function SetMnRelation($oClass1, $oClassGlue, $oClass2){
        $bSucc = false;
        do{

            // Convert class names to Class objects and check the existence of the class.
            if( !($oClass1 = $this->ConvertAndCheckClass($oClass1)) ){ $this->SetError("!oClass1 in ".__METHOD__." @ ".__LINE__); break; }
            if( !($oClass2 = $this->ConvertAndCheckClass($oClass2)) ){ $this->SetError("!oClass2 in ".__METHOD__." @ ".__LINE__); break; }
            if( !($oClassGlue = $this->ConvertAndCheckClass($oClassGlue)) ){ $this->SetError("!oClassGlue in ".__METHOD__." @ ".__LINE__); break; }

            // Check all classes whether suitable (PRIMARY KEYs etc.)
            if( !$oClass1->IsSuitableForMnRelationSide() ){ $this->SetError("Class ".$oClass1->GetName()." not suitable for M:N side in ".__METHOD__." @ ".__LINE__); break; }
            if( !$oClass2->IsSuitableForMnRelationSide() ){ $this->SetError("Class ".$oClass2->GetName()." not suitable for M:N side in ".__METHOD__." @ ".__LINE__); break; }
            if( !$oClassGlue->IsSuitableForMnRelationGlue() ){ $this->SetError("Class ".$oClassGlue->GetName()." not suitable for M:N glue in ".__METHOD__." @ ".__LINE__); break; }

            // Check whether the types are the same 
            $oIdProp1 = $oClass1->GetIdProperty();
            $oIdProp2 = $oClass1->GetIdProperty();
            $aoIdProps = $oClassGlue->GetIdProperties();

            if( $oIdProp1->GetColType()->GetType() != $aoIdProps[0]->GetColType()->GetType() ){
                $this->SetError('Property types mismatch: "'.$oIdProp1->GetName().'" and "'.$aoIdProps[0]->GetName().' in '.__METHOD__.' @ '.__LINE__); break; }
            if( $oIdProp2->GetColType()->GetType() != $aoIdProps[1]->GetColType()->GetType() ){
                $this->SetError('Property types mismatch: "'.$oIdProp2->GetName().'" and "'.$aoIdProps[1]->GetName().' in '.__METHOD__.' @ '.__LINE__); break; }

            //$oIdProp1->CompareTo($aoIdProps[0]); // TODO 

            $b = $oClassGlue->SetMnRelationGlue($oClass1, $oClass2, $aoIdProps[0], $aoIdProps[1]);
            if( !$b ){ $this->SetError("!\$oClassGlue->SetMnRelationGlue @ ".__LINE__); break; }

            $bSucc = true;
        }while(false);
        return $bSucc;
    }


    /**<*********************************************************************************************>
  *                                                                                                *
        @param $oObject1 is an object of which related objects of $oClass2 should be returned.
        @param $oClass2 - can be:
            (object)     - the Class will be found by it's class name.
            (cDwOpClass) Class object - that's what we need
            (string)     class name   - the Class will be found in the dictionary.
  *************************************************************************************************/
  function LoadObjectsByMnRelation($oObject, $oClassGlue, $oClass2, $xId=null){
    $bSucc = false;
    do{

            // Convert class names to Class objects and check the existence of the class.
            if( !($oClass1 = $this->ConvertAndCheckClass($oObject)) ){ $this->SetError("!oClass1 @ ".__LINE__); break; }
            if( !($oClass2 = $this->ConvertAndCheckClass($oClass2)) ){ $this->SetError("!oClass2 @ ".__LINE__); break; }
            if( !($oClassGlue = $this->ConvertAndCheckClass($oClassGlue)) ){ $this->SetError("!oClassGlue @ ".__LINE__); break; }

            if( !$oClassGlue->IsMnRelationGlue() ){ $this->SetError($oClassGlue->GetName()." is not a M : N relation glue in ".__METHOD__." @ ".__LINE__); break; }

            // Table names 
            $sTable1 = $oClass1->GetTable();
            $sTable2 = $oClass2->GetTable();
            $sTableGlue = $oClassGlue->GetTable();

            // Column names 
            $oClass1IdProp = $oClass1->GetIdProperty();
            $sIdCol1 = $oClass1IdProp->GetColName();

            $sIdCol2 = $oClass2->GetIdProperty()->GetColName();
            $sGluePropFrom = $oClassGlue->oMnRelationProp1->GetColName();
            $sGluePropTo   = $oClassGlue->oMnRelationProp2->GetColName();

            if( is_object($oObject) )
                $xId = $oClass1IdProp->FormatForSql( $oObject->GetId() );

            //if( !$xId ){ $this->SetError("Bad ID [$xId] in ".__METHOD__." @ ".__LINE__); break; }


            // Create SQL 
            $sSQL = "
            -- Vypise reklamy a k nim kanaly, ktere jsou s reklamou spojeny.
            SELECT table_to.*
            FROM $sTable1 AS table_from
            INNER JOIN $sTableGlue AS glue ON table_from.$sIdCol1 = glue.$sGluePropFrom
            LEFT JOIN $sTable2 AS table_to ON glue.$sGluePropTo = table_to.$sIdCol2
            WHERE table_from.$sIdCol1 = $xId
            ;";

            $aoObjects = $this->LoadObjectsBySql($oClass2, $sSQL);

            $bSucc = true;
    }while(false);

    return $aoObjects;
    }




}// cDwObjectPersistence 




/**<***********************************************************************>
*  cDwOpClass                                                              *
***************************************************************************/
class cDwOpClass {

    var $sName;
    function GetName()      { return $this->sName; }
    function SetName($sName){ $this->sName = $sName; }

    var $sTable;
    function GetTable()       { return $this->sTable; }
    function SetTable($sTable){ $this->sTable = $sTable; }

    var $oParentClass = null;
    function GetParentClass()             { return $this->oParentClass; }
    function _SetParentClass($oParentClass){ $this->oParentClass = $oParentClass; }



    // -- Constructor -- //
    function cDwOpClass( $sName, $sTable ){ $this->sName = $sName; $this->SetTable($sTable); }

    // Properties //
    var $aoProperties;
    /** @returns cDwOpProperty[] array of this class' properties. */
    function GetProperties()             { return $this->aoProperties; }
    /** @returns cDwOpProperty this class' property of given name or NULL if the class does not have such. */
    function GetProperty($sColName){
        if( isset($this->aoProperties[$sColName]) )
            $oRet = $this->aoProperties[$sColName];
        else $oRet = null;
        return $oRet;
    }
    function AddPropertyObject($oProperty){ return $this->aoProperties[$oProperty->GetColName()] = $oProperty; }
    function AddProperty($sColName, $oColType){
        $oProp = new cDwOpProperty($sColName, $oColType);
        $this->aoProperties[$sColName] = $oProp;
        return $oProp;
    }
    function RemProperty($sColName){
        if( !isset($this->aoProperties[$sColName]) ) return false;
        unset($this->aoProperties[$sColName]);
        return true;
    }
    function GetNonKeyProperties(){
        $aoProps = Array();
        foreach( $this->aoProperties as $oProp ){
            //if( $oProp->IsKeyProp() ) continue;
            $aoProps[] = $oProp;
        }
        return $aoProps;
    }



    // --- M : N relation stuff --- //


    // -- Is this class M : N relation "glue"? -- //
    var $bMnRelationGlue = false;
    function IsMnRelationGlue()            { return $this->bMnRelationGlue; }
    function UnsetMnRelationGlue(){ $this->bMnRelationGlue = false; }

    var $oMnRelationClass1 = null;
    var $oMnRelationClass2 = null;
    function GetMnRelationClass1()                  { return $this->oMnRelationClass1; }
    function GetMnRelationClass2()                  { return $this->oMnRelationClass2; }
    function GetMnRelationOtherClass($oClass){
        if( $oClass === $this->oMnRelationClass1 ) return $this->oMnRelationClass2;
        if( $oClass === $this->oMnRelationClass2 ) return $this->oMnRelationClass1;
        return null;
    }

    var $oMnRelationProp1 = null;
    var $oMnRelationProp2 = null;
    function GetMnRelationProp1()                 { return $this->oMnRelationProp1; }
    function GetMnRelationProp2()                 { return $this->oMnRelationProp2; }
    function GetMnRelationOtherProp($oProp){
        if( $oProp === $this->oMnRelationProp1 ) return $this->oMnRelationProp2;
        if( $oProp === $this->oMnRelationProp2 ) return $this->oMnRelationProp1;
        return null;
    }
    function GetMnRelationPropForClass($oClass){
        //App::Log($oClass->GetName()." ==? ".$this->oMnRelationClass1->GetName());///
        //App::Log($oClass->GetName()." ==? ".$this->oMnRelationClass2->GetName());///
        //if( $oClass === $this->oMnRelationClass1 ) return $this->oMnRelationProp1;
        //if( $oClass === $this->oMnRelationClass2 ) return $this->oMnRelationProp2;
        $asParents = class_parents($oClass->GetName());
        array_unshift($asParents, $oClass->GetName());
        //App::Log(implode(',',$asParents)." <==? ".$this->oMnRelationClass1->GetName());///
        //App::Log(implode(',',$asParents)." <==? ".$this->oMnRelationClass2->GetName());///
        if( in_array($this->oMnRelationClass1->GetName(), $asParents)){ return $this->oMnRelationProp1; }
        if( in_array($this->oMnRelationClass2->GetName(), $asParents)){ return $this->oMnRelationProp2; }
        return null;
    }




    function IsSuitableForMnRelationGlue(){ return Count($this->aoIdProperties) == 2; }
    function IsSuitableForMnRelationSide(){ return Count($this->aoIdProperties) == 1; }

    function SetMnRelationGlue($oClass1, $oClass2, $oProp1, $oProp2){
        $this->bMnRelationGlue = false;
        do{
            // Check everything and return false on failure. 
            if( !$this->IsSuitableForMnRelationGlue() ) return false;
            if( !$oClass1 || !($oClass1 instanceof cDwOpClass) ) return false;
            if( !$oClass2 || !($oClass2 instanceof cDwOpClass) ) return false;

            if( 1 != $oClass1->GetIdPropertiesCount() ) return false;
            if( 1 != $oClass2->GetIdPropertiesCount() ) return false;

            if( is_string($oProp1) ) $oProp1 = $this->GetProperty($oProp1);
            if( is_string($oProp2) ) $oProp2 = $this->GetProperty($oProp2);

            if( null == $oProp1  ||  !($oProp1 instanceof cDwOpProperty) ) return false;
            if( null == $oProp2  ||  !($oProp2 instanceof cDwOpProperty) ) return false;

            // Seems to be ok, set the relation for this Class... (we have to set the sides, too)
            $this->oMnRelationClass1 = $oClass1;
            $this->oMnRelationClass2 = $oClass2;
            $this->oMnRelationProp1 = $oProp1;
            $this->oMnRelationProp2 = $oProp2;

            $this->bMnRelationGlue = true;

        }while(false);
        return $this->bMnRelationGlue;
    }



    // --- ID properties --- //

    // ID property //
    //var $oIdProperty;
    function GetIdProperty()            { reset($this->aoIdProperties); return current($this->aoIdProperties); }
    function SetIdProperty($oIdProperty){
        if(!$oIdProperty){ $this->aoIdProperties = Array(); return; }
        $this->aoIdProperties = Array( $oIdProperty );
        $oIdProperty->SetPrimaryKeyPart(true);
    }

    var $aoIdProperties = Array();
    function GetIdProperties()            { return $this->aoIdProperties; }

    function GetIdPropertiesCount()       { return Count($this->aoIdProperties); }


    /**<**********************************************************************************>
    *  @returns whether Class has given ID property  or ID property of given name         *
    **************************************************************************************/
    function HasIdProperty($xProp){
        if( is_string($xProp) ) $xProp = $this->GetProperty($xProp);
        if( !$xProp ) return false;
        if( in_array($xProp, $this->aoIdProperties) ) return true;
        return false;
    }

    function AddIdProperty($oProperty){ array_push($this->aoIdProperties, $oProperty); $oProperty->SetPrimaryKeyPart(true); }

    /**  @returns string Coma separated list of ID (PRIMARY KEY) values of the given object. */
    function GetIdPropertiesSql($oObject, $sGlue=', '){
        $asPropSqls = Array();
        foreach( $this->GetIdProperties() as $oProp ){
            $asPropSqls[] = $oProp->GetColName() . " = " . $oProp->FormatForSql( $oObject->GetProperty($oProp->GetName()) );
        }
        $saPropSqls = implode($sGlue, $asPropSqls);
        //if( '' != $saPropSqls ) $saPropSqls = " WHERE $saPropSqls "; // We don't want this for ID - can be "ON ...", e.g.
        return $saPropSqls;
    }

    /**  @returns string Formatted list of ID (PRIMARY KEY) values of the given object. */
    function GetObjectMultiIdString($oObject, $iMode=DWOP_VALUE_DUMP_MODE__SQL){
        switch($iMode){
            default: trigger_error("Bad param 2 - must be one of DWOP_VALUE_DUMP_MODE__* constants"); return null; break;
            case DWOP_VALUE_DUMP_MODE__SQL:            $sGlue=', '; $sEq=' = '; $fFunc='sql'; break;
            case DWOP_VALUE_DUMP_MODE__SQL_SHORT:      $sGlue=',';  $sEq='=';   $fFunc='sql'; break;
            case DWOP_VALUE_DUMP_MODE__SQL_VALUES:     $sGlue=',';  $sEq='';    $fFunc='sql'; break;
            case DWOP_VALUE_DUMP_MODE__ESCAPED_VALUES: $sGlue='\''; $sEq='';    $fFunc='addslashes'; break; // Shortest unique 
            case DWOP_VALUE_DUMP_MODE__COMA:           $sGlue=', '; $sEq='';    $fFunc=''; break;
        }
        $asPropSqls = Array();
        foreach( $this->GetIdProperties() as $oProp ){
            $s = '';
            if($sEq) $s .= $oProp->GetColName() . $sEq; // <col name> = 

            $xVal = $oObject->GetProperty($oProp->GetName());
            if(!$fFunc) ;
            elseif( 'sql' == $fFunc )  $xVal = $oProp->FormatForSql( $xVal );
            else/*if( function_exists($fFunc) ) */$xVal = $fFunc($xVal);
            //else ;
            $s .= $xVal;

            $asPropSqls[] = $s;
        }
        $saPropSqls = implode($sGlue, $asPropSqls);
        //if( '' != $saPropSqls ) $saPropSqls = " WHERE $saPropSqls "; // We don't want this for ID - can be "ON ...", e.g.
        return $saPropSqls;
    }


    /**  Sets ID properties.
         @param $xProperties can be:
                    - a Property name 
                - coma separated list of Property names 
                    - array of Property objects 
    */
    function SetIdProperties($xProperties){

        // If $xProperties param is string, convert to an array 
        if( is_string($xProperties) )
            $xProperties = array_map('trim', explode(',', $xProperties));

        // Now the $xProperties must be an array.
        if( !is_array($xProperties) ){
            $this->aoIdProperties = Array();
            return false;
        }



        // Go through all properties in the array and transform them to cDwOpIdingInfo objects. 
        $aoIdProperties = Array();
        foreach( $xProperties as $xProperty ){
            $bDesc = false;
            // If the array item is a string, convert to a property object.
            if( is_string($xProperty) )
                $xProperty = $this->GetProperty($xProperty);

            // If the $xProperty is not a cDwOpProperty object, move on.
            if( null === $xProperty || !($xProperty instanceof cDwOpProperty) )
                continue;

            $xProperty->SetPrimaryKeyPart(true);
            $aoIdProperties[] = $xProperty;
        }

        $this->aoIdProperties = $aoIdProperties;
        //echo "<pre>".AdjustedPrintR($aoOrderProperties)."</pre>";///

    }// SetIdProperties($xProperties)




    // --- Order properties --- //
    var $aoOrderProperties = Array();
    function GetOrderProperties()                  { return $this->aoOrderProperties; }


    /**  @returns string Coma separated list of Order Properties (column names). */
    function GetOrderPropertiesSql(){
        $asPropSqls = Array();
        foreach( $this->aoOrderProperties as $oOrderingInfo ){
            $asPropSqls[] = $oOrderingInfo->oProperty->GetColName() . ($oOrderingInfo->bDesc ? ' DESC' : ' ASC');
        }
        $saPropSqls = implode(', ', $asPropSqls);
        if( '' != $saPropSqls ) $saPropSqls = " ORDER BY $saPropSqls ";
        return $saPropSqls;
    }


    /**<*************************************************************************>
    *  Sets the ordering information for this class. Previous info is replaced.  *
       @param $xProperties can be a coma separated list of Property names,        
                                    an array of Property names, or
                                    an array of Property objects.
    *****************************************************************************/
    function SetOrderProperties($xProperties){
        // If $xProperties param is string, convert to an array 
        if( is_string($xProperties) )
            $xProperties = array_map('trim', explode(',', $xProperties));

        // Now the $xProperties must be an array.
        if( !is_array($xProperties) )
            return false;

        // Go through all properties in the array and transform them to cDwOpOrderingInfo objects. 
        $aoOrderProperties = Array();
        foreach( $xProperties as $xProperty ){
            $bDesc = false;
            // If the array item is a string, convert to a property object.
            if( is_string($xProperty) ){
                if( '' == $xProperty ) continue;
                if( $xProperty[0] == '+' || $xProperty[0] == '-' ){
                    $bDesc = ( $xProperty[0] == '-' );
                    $xProperty = substr($xProperty, 1);
                }
                $xProperty = $this->GetProperty($xProperty);
            }

            // Instance of cDwOpProperty - convert to cDwOpOrderingInfo
            if( $xProperty instanceof cDwOpProperty ){
                $xProperty = new cDwOpOrderingInfo($xProperty, $bDesc);
            }

            // If the $xProperty is not a cDwOpProperty object, move on.
            if( null === $xProperty || !($xProperty instanceof cDwOpOrderingInfo) )
                continue;

            $aoOrderProperties[] = $xProperty;
        }

        $this->aoOrderProperties = $aoOrderProperties;
        //echo "<pre>".AdjustedPrintR($aoOrderProperties)."</pre>";///

    }// SetOrderProperties($xProperties)


}// class cDwOpClass 






/**<***********************************************************************>
*  Holds information about ordering property - for cDwOpClass.             *
***************************************************************************/
class cDwOpOrderingInfo {
    var $oProperty = null;
    var $bDesc = false;
    function cDwOpOrderingInfo($oProperty, $bDesc=false){
        $this->oProperty = $oProperty;
        $this->bDesc = $bDesc;
    }
}




/**<***********************************************************************>
*  cDwOpProperty                                                           *
***************************************************************************/
class cDwOpProperty {

    /**  Name - currently we use the same as a Property name and database column name.  */
    //var $sName = null;
    function GetName()         { return $this->sColName; }
    function SetName($sColName){ $this->sColName = $sColName; } 

    /**  Column name - currently used also as the name of the property  */
    var $sColName = null;
    function GetColName()         { return $this->sColName; }
    function SetColName($sColName){ $this->sColName = $sColName; }

    /**  Column type - an object derived from cDwOpColumnType class.  */
    var $oColType = null;
    /** @returns cDwOpColumnType column type representing this property. */
    function GetColType()         { return $this->oColType; }
    function SetColType($oColType){ $this->oColType = $oColType; }

    /**  Whether the property is a part of a PRIMARY KEY for this Class.  */
    var $bPrimaryKeyPart = false;
    function IsPrimaryKeyPart()                { return $this->bPrimaryKeyPart; }
    function SetPrimaryKeyPart($bPrimaryKeyPart){ $this->bPrimaryKeyPart = $bPrimaryKeyPart; }

    /**  Whether the property is a FOREIGN KEY.  */
    var $bForeignKey = false;
    function IsForeignKey()            { return $this->bForeignKey; }
    function SetForeignKey($bForeignKey){ $this->bForeignKey = $bForeignKey; }

    /**  Whether the property is unique across all objects of this Class.  */
    var $bUnique = false;
    function IsUnique()         { return $this->bUnique; }
    function SetUnique($bUnique){ $this->bUnique = $bUnique; }

    /**  Whether this property can be NULL.  */
    var $bCanBeNull = false;
    function IsNull()            { return $this->bCanBeNull; }
    function SetNull($bCanBeNull){ $this->bCanBeNull = $bCanBeNull; }

    function IsKey(){
        return $this->IsPrimaryKeyPart() || $this->IsForeignKey() || $this->IsUnique();
    }


    // -- Constructor -- //
    function cDwOpProperty($sColName, $oColType, $bUnique=false){
        $this->SetColName($sColName);
        $this->SetColType($oColType);
        $this->SetUnique($bUnique);
    }

    /**  @returns SQL literal representing a value $xVal if it was held by this property. */
    function FormatForSql($xVal){
        if( null === $xVal  &&  $this->GetColType()->IsNull() )
            return 'NULL';
        //echo "\n".CallInfo()."; Val: ".$xVal." ColType:".$this->GetColType()->GetName();///
        return $this->GetColType()->FormatForSql($xVal);
    }

}// class cDwOpProperty 








/**<***********************************************************************>
*  cDwOpColumnType                                                         *
***************************************************************************/
abstract class cDwOpColumnType {

    var $sName;
    function GetName()      { return $this->sName; }
    function SetName($sName){ $this->sName = $sName; }

    var $iType;
    /** @returns  one of type constants: DWOP_TYPE_STR,DWOP_TYPE_NUM,DWOP_TYPE_BOOL,DWOP_TYPE_ENUM,DWOP_TYPE_SET, DWOP_TYPE_REAL */
    function GetType()      { return $this->iType; }
    /** @param iType  one of type constants: DWOP_TYPE_STR,DWOP_TYPE_NUM,DWOP_TYPE_BOOL,DWOP_TYPE_ENUM,DWOP_TYPE_SET, DWOP_TYPE_REAL */
    function SetType($iType){
        if(!in_array($iType, Array(DWOP_TYPE_STR,DWOP_TYPE_NUM,DWOP_TYPE_BOOL,DWOP_TYPE_ENUM,DWOP_TYPE_SET, DWOP_TYPE_REAL))){
            user_error("cDwOpColumnType::SetType(): Param \$iType must be some of DWOP_TYPE_* constants.");
        }
        $this->iType = $iType;
    }

    // Can be NULL?  
    var $bCanBeNull = true;
    function IsNull()            { return $this->bCanBeNull; }
    function SetNull($bCanBeNull){ $this->bCanBeNull = $bCanBeNull; }

    // Default value 
    var $xDefault;
    function GetDefault()         { return $this->xDefault; }
    function SetDefault($xDefault){
        $bRet = true;
        if( (null === $xDefault && !$this->IsNull() )  // Default value being set is NULL and must not be null
         || ( !$this->CheckValue($xDefault) )          // or  the default value is not allowed for this type,
        ){
            $xDefault = $this->GetDefaultDefault();      // then set the default to the default default,
            $bRet = false;                               // and indicate failure by returning false.
        }
        $this->xDefault = $xDefault;
        return $bRet;
    }
    //function GetDefaultDefault(){ user_error(__METHOD__.' must be overriden.'); return null; }
    function GetDefaultDefault(){
        switch( $this->GetType() ){
            case DWOP_TYPE_STR:  return '';  break;
            case DWOP_TYPE_NUM:  return 0;   break;
            case DWOP_TYPE_BOOL: return false; break;
            case DWOP_TYPE_ENUM: return '';  break;
            case DWOP_TYPE_SET:  return '';  break;
            case DWOP_TYPE_REAL: return 0.0; break;
        }
    }


    // Aditional data - possible values for ENUM and SET, etc
    //var $aData;



    /**<***********************************************************************>
    *  Constructor                                                             *
    ***************************************************************************/
    function cDwOpColumnType($iFlags=0, $xDefault=null){

        // Flags 
        if( $iFlags & DWOP_NOT_NULL )
            $this->SetNull(false);
        //if( $iFlags & DWOP_BINARY )
        //  $this->SetBinary(true);

        // Default value 
        $this->SetDefault( $xDefault );

    }// cDwOpColumnType::cDwOpColumnType()


    // Useless 
    /*function cDwOpColumnType($sName, $iType, $aData){
        $this->SetName($sName);
        $this->SetType($iType);
        $this->aData = $aData;
    }// cDwOpColumnType::cDwOpColumnType()
    /**/


    /**<***********************************************************************>
    *  @returns the value in $xVal in the format suitable for SQL.             *
    ***************************************************************************/
    function FormatForSql($xVal){
        //App::Log( CallInfo()."     \$xVal: (".gettype($xVal).") $xVal" );
        $sRet = null;

        if( null === $xVal ){
            if( $this->IsNull() )
                $sRet = 'NULL';
            else
                $sRet = 'nULl';
        }
        else switch($this->GetType()){
            case DWOP_TYPE_STR:  $sRet = asq((string)$xVal); break;
            case DWOP_TYPE_NUM:  $sRet = (double)$xVal; /*echo "<br>XXX".CallInfo(-2)."<br>".CallInfo(-3);*/ break;
            case DWOP_TYPE_BOOL: $sRet = (integer)(boolean)$xRet; break;
            case DWOP_TYPE_ENUM: $sRet = asq((string)$xVal); break;
            case DWOP_TYPE_SET:  $sRet = asq((string)$xVal); break;
            case DWOP_TYPE_REAL: $sRet = (double)$xVal; break;
            //case DWOP_TYPE_:  $sRet = ; break;
            default: trigger_error('Unknown value type! in '.__METHOD__.' @ '.__LINE__); $sRet = asq((string)$xVal); break;
        }
        //App::Log( CallInfo()."     \$sRet: $sRet" );
        return $sRet;
    }

    function ConvertFromResult($xVal){ return $xVal; }


    /**<***********************************************************************>
    *  Check whether the variable can fit in the column.                       *
    ***************************************************************************/
    function CheckValue($xVal /*, $oProperty*/){

        if( null === $xVal && !$this->IsNull() )
            return false;

        $bFits = true;      
        switch($this->GetType()){
            case DWOP_TYPE_STR:  $bFits = is_string($xVal) || is_numeric($xVal); break;
            case DWOP_TYPE_NUM:  $bFits = is_bool($xVal) || is_int($xVal); break;
            case DWOP_TYPE_BOOL: $bFits = is_bool($xRet) || is_int($xVal); break;
            //case DWOP_TYPE_ENUM: $bFits = is_string($xVal) /*&& in_array($xVal, $this->asMembers)/**/; break;
            case DWOP_TYPE_SET:
                $asMembers = array_map('trim', explode(',', $xVal));
                foreach( $asMembers as $sMember ){
                    if( !in_array($xVal, $this->aData) ){ $bFits = false; break; }
                }
            break;
        }
        return $bFits;
    }

}// class cDwOpColumnType 






/**<***********************************************************************>
*  cDwOpColumnTypesRepository                                              *
   Skladiste casto pouzivanych typu sloupcu.
     Nema cenu sem davat stringy - lisi se delkou.
     Mozna je to uplne zbytecna trida...
***************************************************************************/
/*class cDwOpColumnTypesRepository {
    var $aoColumnTypes;
    function AddColumnType($sKey, &$oType){ $this->aoColumnTypes[$sKey] =& $oType; }


    function cDwOpColumnTypesRepository(){
        $this->aoColumnTypes = Array();

        $oType =& new cDwOpColumnType_INT();
        $this->AddColumnType('INT', $oType);
        $oType =& new cDwOpColumnType_INT_UNSIGNED();
        $this->AddColumnType('INT UNSIGNED', $oType);
        $oType =& new cDwOpColumnType_SMALLINT();
        $this->AddColumnType('SMALLINT', $oType);
        $oType =& new cDwOpColumnType_SMALLINT_UNSIGNED();
        $this->AddColumnType('SMALLINT UNSIGNED', $oType);


    }// cDwOpColumnTypesRepository()

    function &GetColumnType($sName){
        return isset( $this->aoColumnTypes[$sName] ) ? $this->aoColumnTypes[$sName] : null;
    }

}// class cDwOpColumnTypesRepository 
/**/






/** ************************************************************************
*  Simple class that holds item's ID and a hashmap of parameters.          *
***************************************************************************/
abstract class DwFwIdAndProperties {

    function DwFwIdAndProperties($iId=null){
        $this->SetId($iId);
    }

    var $sId;
    function GetId()    { return $this->sId; }
    function SetId($sId){ $this->sId = $sId; }

    var $asParams = Array();
    function SetProperty($sName, $sValue){ $this->asParams[(string)$sName] = $sValue; }
    function GetProperty($sName){ return isset($this->asParams[(string)$sName]) ? $this->asParams[(string)$sName] : null; }
    //function HasProperty($sName){ return isset($this->asParams[(string)$sName]); }
    function HasProperty($sName){ return array_key_exists((string)$sName, $this->asParams); }
    function IsPropertySet($sName){ return $this->HasProperty($sName); }

    function GetProperties(){ return $this->asParams; }
    function SetProperties($asParams, $bOverwrite=true){
        foreach( $asParams as $sName => $sValue ){
            if($bOverwrite || !isset($this->asParams[(string)$sName]))
                $this->asParams[(string)$sName] = $sValue;
        }
    }

    function DwFwIdAndParams($sId=null){ $this->sId = $sId; }

}// class DwFwIdAndParams 
\--






Usage - model
=============


/--code php .[brush:]
/** ************************************************************************
*  Simple class that holds item's ID and a hashmap of parameters.          *
***************************************************************************/
abstract class DwFwIdAndProperties {

    function DwFwIdAndProperties($iId=null){
        $this->SetId($iId);
    }

    var $sId;
    function GetId()    { return $this->sId; }
    function SetId($sId){ $this->sId = $sId; }

    var $asParams = Array();
    function SetProperty($sName, $sValue){ $this->asParams[(string)$sName] = $sValue; }
    function GetProperty($sName){ return isset($this->asParams[(string)$sName]) ? $this->asParams[(string)$sName] : null; }
    //function HasProperty($sName){ return isset($this->asParams[(string)$sName]); }
    function HasProperty($sName){ return array_key_exists((string)$sName, $this->asParams); }
    function IsPropertySet($sName){ return $this->HasProperty($sName); }

    function GetProperties(){ return $this->asParams; }
    function SetProperties($asParams, $bOverwrite=true){
        foreach( $asParams as $sName => $sValue ){
            if($bOverwrite || !isset($this->asParams[(string)$sName]))
                $this->asParams[(string)$sName] = $sValue;
        }
    }

    function DwFwIdAndParams($sId=null){ $this->sId = $sId; }

}// class DwFwIdAndParams 





/***************************************************************************
*  User                                                                    *
***************************************************************************/
class cObjectPersistenceTestClass_User extends DwFwIdAndProperties {
    function GetProperty($sName) {
        if( 'id' == $sName ) return $this->GetId();
        else                 return parent::GetProperty($sName);
    }
    function SetProperty($sName, $sValue){
        if( 'id' == $sName ) return $this->SetId($sValue);
        else                 return parent::SetProperty($sName, $sValue);
    }
}// class cObjectPersistenceTestClass_User 

/***************************************************************************
*  Door                                                                    *
***************************************************************************/
class cObjectPersistence_TestClass_Door extends DwFwIdAndProperties {
    function GetProperty($sName) {
        if( 'id' == $sName ) return $this->GetId();
        else                 return parent::GetProperty($sName);
    }
    function SetProperty($sName, $sValue){
        if( 'id' == $sName ) return $this->SetId($sValue);
        else                 return parent::SetProperty($sName, $sValue);
    }
}// class cObjectPersistenceTestClass_User 

/***************************************************************************
*  Key                                                                     *
***************************************************************************/
class cObjectPersistence_TestClass_Key extends DwFwIdAndProperties {
    function GetProperty($sName) {
        if( 'id' == $sName ) return $this->GetId();
        else                 return parent::GetProperty($sName);
    }
    function SetProperty($sName, $sValue){
        if( 'id' == $sName ) return $this->SetId($sValue);
        else                 return parent::SetProperty($sName, $sValue);
    }
}// class cObjectPersistenceTestClass_User 
\--






Usage - data manipulation
=========================


/--code php .[brush:]
// Create DB object 
$oDB = new cDBAccess_MySQL('localhost:3350', 'test', 'test', 'test', 'cp1250');
$oDB->SetSelectMode(CDBA_SELECT_RETURNS_CRESULT_ON_ERROR);
echo "Connect: ".///
    $oDB->Connect();
echo "<br/>\n";///

//$xVal = $oDB->SelectCell("SELECT NULL"); // Test what NULL looks like in PHP 
//echo "\n".gettype($xVal)." ".ord($xVal); die();

// Create Object Persistence object 
$oOP = new cDwObjectPersistence_DB($oDB);
//$oColTypes =& new cDwOpColumnTypesRepository();
\--


Example SQL schema & data
=========================


/--code sql .[brush:]
CREATE TABLE `ap_users` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `user` varchar(255) NOT NULL,
  `pass` varchar(40) default NULL,
  `fname` varchar(255) NOT NULL,
  `lname` varchar(255) NOT NULL,
  `addr` varchar(255) NOT NULL,
  `city` varchar(255) NOT NULL,
  `state` enum('good', 'bad') NOT NULL DEFAULT 'good',
  `psc` varchar(255) NOT NULL,
  `ctry` tinyint(3) unsigned NOT NULL,
  `ppal` varchar(255) default NULL
);
INSERT INTO ap_users (id, user) VALUES
  (10001, 'Ondra')
, (10002, 'Zdenek')
, (10003, 'Martin')
, (10004, 'Zuzka')
, (10005, 'David')
, (10006, 'Satan')
;

CREATE TABLE ap_doors (
    id INT UNSIGNED NOT NULL auto_increment PRIMARY KEY,
    txt VARCHAR(15)
);
INSERT INTO ap_doors VALUES (330, 'Nebe'), (118, 'Bazina'), (116, 'Peklo'), (110, 'Curaprox'), (222, 'Neznamo');

CREATE TABLE ap_keys (
    id_user INT UNSIGNED NOT NULL,
    id_door INT UNSIGNED NOT NULL,
    PRIMARY KEY pk( id_user, id_door )
);
INSERT INTO ap_keys VALUES
(10001, 330), (10001, 118), (10001, 110),    -- Ondra 
(10002, 118), (10002, 116),                  -- Zdenek
(10003, 118), (10003, 330), (10003, 116),    -- Martin
(10004, 110), (10004, 330),                  -- Zuzka 
(10006, 116), (10006, 118);                  -- Satan
\--


/--code php .[brush:]
$oClass = new cDwOpClass('cObjectPersistenceTestClass_User', 'ap_users');
//$oProp = $oClass->AddProperty('id', $oColTypes->GetColumnType('INT UNSIGNED'));
    $oProp = $oClass->AddProperty('id', new cDwOpColumnType_INT_UNSIGNED() );
    $oClass->SetIdProperty($oProp);
    $oProp = $oClass->AddProperty('user',  new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('pass',  new cDwOpColumnType_VARCHAR(40,  DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('fname', new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('lname', new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('addr',  new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('city',  new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('state', new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('psc',   new cDwOpColumnType_VARCHAR(255, DWOP_NOT_NULL) );
    $oProp = $oClass->AddProperty('ctry',  new cDwOpColumnType_TINYINT_UNSIGNED() );
    $oProp = $oClass->AddProperty('ppal',  new cDwOpColumnType_VARCHAR(255) );
    $oClass->SetOrderProperties('lname, +fname, -id');
$oOP->AddClassIntoDictionary($oClass);
/*/
$oClass = $oOP->CreateClassByTable('cObjectPersistenceTestClass_User', 'ap_users');
$oClass->SetOrderProperties('lname, +fname, -id');
//file_put_contents('x1.txt', AdjustedPrintR($oClass));///
//echo "<pre>\$oClass: ".AdjustedPrintR($oClass)."</pre>";///
\--





Usage - data manipulation
=========================

/--code php .[brush:]
srand(time());

echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";

// Load by ID 
echo "<h3>Load by ID</h3>";
$iID = 1;
echo "<pre>\$oObject = \$oOP->LoadObjectById(".$oClass->GetName().", $iID);</pre>";
$oUser = $oOP->LoadObjectById($oClass, $iID);
echo "<pre>oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";


// Load by ID, using class name string 
echo "<h3>Load by ID, using class name string</h3>";
$iID = 1;
echo "<pre>\$oObject =& \$oOP->LoadObjectById('".$oClass->GetName()."', $iID);</pre>";
$oUser =& $oOP->LoadObjectById($oClass->GetName(), $iID);
echo "<pre>oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";



// Load by value 
echo "<h3>Load by value</h3>";
$xVal = 'as';
echo "<pre>\$aoUsers = \$oOP->LoadObjectsByValue(".$oClass->GetName().", 'user', $xVal);</pre>";
$aoUsers = $oOP->LoadObjectsByValue($oClass, 'user', $xVal);
echo "<pre>\$aoUsers: [".gettype($aoUsers)."]".AdjustedPrintR($aoUsers)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";



// Save 
echo "<h3>Save</h3>";
$oUser = new cObjectPersistenceTestClass_User();
$oUser->SetId(1);
$oUser->SetProperty('user', 'as'.rand());
$oUser->SetProperty('pass', 'as');
$oUser->SetProperty('fname', 'Astar');
$oUser->SetProperty('lname', 'Seran');
echo "<pre>oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<pre>\$oOP->SaveObject(\$oUser);</pre>";
$bSucc = $oOP->SaveObject($oUser);
echo "<div>".($bSucc ? 'saved' : 'error')."</div>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";


// Create 
echo "<h3>Create</h3>";
$oUser = new cObjectPersistenceTestClass_User();
$oUser->SetId(null);
$oUser->SetProperty('user', 'as'.rand());
$oUser->SetProperty('pass', 'as');
$oUser->SetProperty('fname', 'Astar');
$oUser->SetProperty('lname', 'Seran');
echo "<pre>oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<pre>\$oOP->SaveObject(\$oUser);</pre>";
$bSucc = $oOP->SaveObject($oUser);
echo "<div>".($bSucc ? 'saved' : 'error')."</div>";
if(!$bSucc) echo "<pre>".$oOP->GetDB()->GetLastErrorString()."</pre>";
echo "<pre>\$oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";


// Load by value - load object created above -> Object Pool hit 
echo "<h3>Load by value 2</h3>";

$xVal = $oUser->GetProperty('user');
echo "<pre>\$aoUsers = \$oOP->LoadObjectsByValue(".$oClass->GetName().", 'user', $xVal);</pre>";
$aoUsers = $oOP->LoadObjectsByValue($oClass, 'user', $xVal);
echo "<pre>\$aoUsers: [".gettype($aoUsers)."]".AdjustedPrintR($aoUsers)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";



// Remove from pool - still that one object 
echo "<h3>Remove from pool</h3>";

echo "<pre>\$bSucc = \$oOP->RemObjectFromPool(\$oUser);</pre>";
$bSucc = $oOP->RemObjectFromPool($oUser);
echo "<pre>\$oUser: [".gettype($oUser)."]".AdjustedPrintR($oUser)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";


// Load by value - that object again 
echo "<h3>Load by value 3</h3>";

$xVal = $oUser->GetProperty('user');
echo "<pre>\$aoUsers = \$oOP->LoadObjectsByValue(".$oClass->GetName().", 'user', $xVal);</pre>";
$aoUsers = $oOP->LoadObjectsByValue($oClass, 'user', $xVal);
echo "<pre>\$aoUsers: [".gettype($aoUsers)."]".AdjustedPrintR($aoUsers)."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";

// Delete that object from DB 
echo "<h3>Delete object by ID</h3>";

echo "<pre>\$oOP->DeleteObject(".$oClass->GetName().", ".$oUser->GetId().");</pre>";
$bSucc = $oOP->DeleteObject($oClass, $oUser->GetId());
if(!$bSucc) echo '<div style="font-color: red;">Delete failed. Error: '.$oOP->GetError()."</pre>";
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";



// Load all objects 
echo "<h3>Load all objects</h3>";

echo "<pre>\$aoUsers = \$oOP->LoadObjects(".$oClass->GetName().");</pre>";
$aoUsers = $oOP->LoadObjects($oClass);
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";
//echo "<pre>\$aoUsers: [".gettype($aoUsers)."]".AdjustedPrintR($aoUsers)."</pre>";




// M : N relation 
$oClassUser = $oClass;
echo "<h2>M : N Relation</h2>";

echo "<div>\$oClassDoor = \$oOP->CreateClassByTable('cObjectPersistence_TestClass_Door', 'ap_doors');</div>";
$oClassDoor = $oOP->CreateClassByTable('cObjectPersistence_TestClass_Door', 'ap_doors');
file_put_contents('oClassDoor.txt', AdjustedPrintR($oClassDoor));///
echo "<div>\$oClassDoor->IsSuitableForMnRelationSide(): ".(int)$oClassDoor->IsSuitableForMnRelationSide()."</div>";
echo "<div>\$oClassUser->IsSuitableForMnRelationSide(): ".(int)$oClassUser->IsSuitableForMnRelationSide()."</div>";

echo "<div>\$oClassKey  = \$oOP->CreateClassByTable('cObjectPersistence_TestClass_Key', 'ap_keys');</div>";
$oClassKey  = $oOP->CreateClassByTable('cObjectPersistence_TestClass_Key', 'ap_keys');
file_put_contents('oClassKey.txt', AdjustedPrintR($oClassKey));///
echo "\$oClassKey->IsSuitableForMnRelationGlue(): ".(int)$oClassKey->IsSuitableForMnRelationGlue();

// Settin'up 
echo '<pre>$oOP->SetMnRelation($oClassUser, $oClassKey, $oClassDoor);</pre>';
$bSucc = $oOP->SetMnRelation($oClassUser, $oClassKey, $oClassDoor);
file_put_contents('oClassKey2.txt', AdjustedPrintR($oClassKey));///
if(!$bSucc) echo "<div>Error: ".$oOP->GetError()."</div>";



// Load objects by M : N relation 

/*/ Determines the Class and the ID from the $oUser object.
echo "<pre>\$aoDoors = \$oOP->LoadObjectsByMnRelation(\$oUser, \$oClassKey, \$oClassDoor)</pre>";
$aoDoors = $oOP->LoadObjectsByMnRelation($oUser, $oClassKey, $oClassDoor);
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";
echo "<pre>\$aoDoors: (".gettype($aoDoors).") ".AdjustedPrintR($aoDoors)."</pre>";
/*/ // The same with ID explicitly set to 10001 
echo "<pre>\$aoDoors = \$oOP->LoadObjectsByMnRelation('cObjectPersistenceTestClass_User', \$oClassKey, \$oClassDoor, 10001)</pre>";
$aoDoors = $oOP->LoadObjectsByMnRelation('cObjectPersistenceTestClass_User', $oClassKey, $oClassDoor, 10001);
echo "<div>GetPoolCount(): ".$oOP->GetPoolCount()."</div>";
echo "<pre>\$aoDoors: (".gettype($aoDoors).") ".AdjustedPrintR($aoDoors)."</pre>";

// Multiple ID properties SQL for an object:
echo "<div>\$oClassDoor->GetIdPropertiesSql(\$aoDoors[0]): ".$oClassDoor->GetIdPropertiesSql($aoDoors[0])."</pre>";

$aoKeys = $oOP->LoadObjectsByValue('cObjectPersistence_TestClass_Key', 'id_user', 10001);
echo "<div>\$oClassKey->GetIdPropertiesSql(\$aoDoors[0]): ".$oClassKey->GetIdPropertiesSql($aoKeys[0])."</pre>";



if( $oOP->GetError() )
echo '<div style="color: red">Error from the past: '.$oOP->GetError()."</div>";
\--


/--code php .[brush:]
\--


/--code php .[brush:]
\--


/--code php .[brush:]
\--


/--code php .[brush:]
\--

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant