Location: PHPKode > scripts > Piragibe > piragibe/PGBRecordSet.php
<?php
/**
 * A generic set of PGBRecord's.
 * 
 * This is not meant to be used "asis". Rather, it should be inherited and enhanced for each
 * kind of source (DBX, plain records, PBO, etc).
 * 
 * @package Piragibe
 * @author Francisco Piragibe
 * @version 1.00
 * @copyright Copyright &copy; 2006, Francisco Piragibe
 * This file is part of The PIRAGIBE Framework.
 *
 * The PIRAGIBE Framework 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.
 * 
 * The PIRAGIBE Framework 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 The PIRAGIBE Framework; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
class PGBRecordSet {
	/**
	 * @var integer the recordset current position
	 */
    var $m_rp;          // posicao corrente do recordset
    /**
     * @var array PGBRecord's that are part of the recordset
     */
    var $m_records;     // array de objetos PGBRecord que compoem o recordset
    /**
     * Each record has its own status, that can be:
     * - 0 (QUERIED) -> the record has been retrieved from a query and has not been modified
     * - 1 (NEW)     -> the record has been created recently
     * - 2 (UPDATED) -> the record has changed since its creation or retrieval
     * - 3 (DELETED) -> the record is logically deleted
     * @var array the status of each record
     */
    var $m_status;      // array com status de cada registro
    //  0 : QUERIED -> o registro foi recuperado de sua base de dados, sem mudancas
    //  1 : NEW     -> o registro deve ser criado na base de dados
    //  2 : UPDATED -> o registro foi atualizado, e as atualizacoes devem ir para a base de dados
    //  3 : DELETED -> o registro deve ser excluido da base de dados
    /**
     * This is metadata for the recordset. It's an array, having the following structure:
     * - 'name' -> array with column names
     * - 'type' -> array with corresponding column types
     * @var array metadata for the recordset (names and types of each record's columns
     */
    var $m_info;        // array com informacoes sobre a estrutura de cada registro no recordset
    //  ['name']        // array com nomes das colunas
    //      [n] -> nome da coluna n
    //  ['type']        // array com tipos de cada coluna
    //      [n] -> tipo da coluna n
    /**
     * @var PGBPrimaryKey a PK definition for the recordset
     */
    var $m_pk;          // objeto PGBPrimaryKey que define a PK do recordset
    /**
     * @var array set of PGBSlaveDefinition's that should be applied to each record
     */
    var $m_slavedefs;   // array de definicoes de recordset's escravos, que devem ser materializados
    // a cada registro
    /**
     * The master field values that link this recordset to a master record.
     * 
     * This only makes sense for slave recordsets (recordsets that are subordinated to a record).
     * The array has the form 'master column name' => 'master column value'.
     * @var array the master fields's values
     */
    var $m_masterkeys;  // array com valores dos campos do registro mestre que serviram de base para a
    // montagem de um recordset escravo
    // [<nome da coluna>] = <valor da coluna pai>
    
    /**
     * Standard constructor.
     */
    function PGBRecordSet() {
        $this->clean();
        $this->m_slavedefs      = array();
        $this->m_pk             = NULL;
        $this->m_masterkeys     = array();
    }

// inicio da secao que o herdeiro pode implementar
	/**#@+
	 * (This should be implemented by each heir).
	 */
	/**
	 * Initializes the recordset, from any generic source of data.
	 * @param mixed $pSource the source of data for the recordset
	 */
    function initialize( $pSource ) {
    // inicializa e monta o recordset a partir de uma fonte qualquer
    }
    
    /**
     * Pre-insert code for the record
     * @param PGBRecord &$pRecord the record to be inserted
     * 
     */
    function preInsert( &$pRecord ) {
    }
    
    /**
     * Post-insert code for the record
     * @param PGBRecord &$pRecord the record to be inserted
     * 
     */
    function postInsert( &$pRecord ) {
    }

    /**
     * Serializes a NEW record to the underlying datasource.
     * @param PGBRecord &$pRecord the record to be inserted
     */
    function dbInsert( &$pRecord ) {
    // cria um registro na base de dados
    }

    /**
     * Pre-update code for the record
     * @param PGBRecord &$pRecord the record to be updated
     * 
     */
    function preUpdate( &$pRecord ) {
    }
    
    /**
     * Post-update code for the record
     * @param PGBRecord &$pRecord the record to be updated
     * 
     */
    function postUpdate( &$pRecord ) {
    }

    /**
     * Serializes an UPDATED record to the underlying datasource.
     * @param PGBRecord &$pRecord the record to be updated
     */
    function dbUpdate( &$pRecord ) {
    // atualiza um registro na base de dados
    }

    /**
     * Pre-delete code for the record
     * @param PGBRecord &$pRecord the record to be deleted
     * 
     */
    function preDelete( &$pRecord ) {
    }
    
    /**
     * Post-delete code for the record
     * @param PGBRecord &$pRecord the record to be deleted
     * 
     */
    function postDelete( &$pRecord ) {
    }

    /**
     * Serializes a DELETED record to the underlying datasource.
     * @param PGBRecord &$pRecord the record to be deleted
     */
    function dbDelete( &$pRecord ) {
    // apaga um registro da base de dados
    }

    /**
     * Applies changes (inserts, updates, deletes) to the underlying datasource.
     * 
     * This uses each record's status as an indicator for the operation to be performed; then
     * it calls dbInsert, dbUpdate or dbDelete, depending on its value. Unlike the other heir
     * specific methods, this has a default implementation, for convenience.
     * 
     */
    function post() {
    // posta inclusoes, atualizacoes e exclusoes na base de dados
    // temos uma implementacao default aqui, que pode sofrer override para maior
    //  eficiencia ou para suporte a particularidades de algum subtipo
        reset( $this->m_records );
        //print '<h2>' . $this->getName() . 'postando</h2>';
        while( list( $k, $r ) = each( $this->m_records ) ) {
        // posta os dados do registro corrente
            $s = $this->m_status[ $k ];
            if ( count( $this->m_masterkeys ) > 0 ) {
                $r->setMasterFieldValues( $this->m_masterkeys );
            }
            if ( $s == 1 ) {
                $this->dbInsert( $r );
            }
            elseif ( $s == 2 ) {
                //print 'atualizando registro (a) ' . $k . '<br>';
                if ( $r->getStatus() == 2 ) {
                    //print 'atualizando registro (b) ' . $k . '<br>';
                    $this->dbUpdate( $r );
                }
            }
            elseif ( $s == 3 ) {
                $this->dbDelete( $r );
            }
            // volta status para "queried"
            $this->m_status[ $k ] = 0;
        // posta os recordsets escravos do registro corrente, se existirem
            $sl = $r->getSlaves();
            while( list( $ks, $rs ) = each( $sl ) ) {
                if ( is_a( $rs, 'PGBRecordSet' ) ) {
                    if ( count( $rs->getMasterKeys() ) <= 0 ) {
                    // neste caso, temos um registro mestre novo que, na mesma passada, sofreu
                    // cadastramento de escravos; a chave estrangeira nao esta definida ainda,
                    // e deve ser setada antes da gravacao dos escravos
                        //print 'POST-SL: ' . $ks . '<br>';
                        $c          = $this->getSlaveDefinition( $ks );
                        $rd         = $c->getRelDefs();
                        $pv         = array();
                        reset( $rd );
                        while( list( $pn, $cn ) = each( $rd ) ) {
                            $v          = $r->getFieldValue( $pn );
                            if ( !is_null( $v ) and $v != '' ) {
                                $pv[ $cn ]  = $v;
                            }
                        }
                        //print '<pre>POST-SL: ' . $c->getName() . '</pre>';
                        $rs->setMasterKeys( $pv );
                    }
                    $rs->post();
                }
            }
        }
    }

// fim da secao que o herdeiro pode implementar
    /**#@- */

    /**
     * Cleans everything up in the recordset.
     */
    function clean() {
    // limpa todas as variaveis membro, zerando tudo no RecordSet
        $this->m_records        = array();
        $this->m_status         = array();
        $this->m_info           = array();
        $this->m_info['name']   = array();
        $this->m_info['type']   = array();
        $this->m_rp             = 0;
    }

    /**
     * Searches for the next non-deleted record, positioning the recordset there.
     * @return boolean true if search successful; false otherwise
     */
    function searchForward() {
    // busca um registro nao marcado como excluido indo para a frente no recordset
        $p  = $this->getRecordPointer();
        $nr = count( $this->m_records );
        $lo = 0;
        do {
            if ( $p >= $nr ) {
                $p = 0;
                $lo++;
            }
            $r = $this->m_records[ $p ];
            $p++;
        } while( $r->getStatus() != 3 and $lo < 2 );
        $this->setRecordPointer( $p - 1 );
        return ( $lo < 2 );
    }

    /**
     * Searches for the previous non-deleted record, positioning the recordset there.
     * @return boolean true if search successful; false otherwise
     */
    function searchBackwards() {
    // busca um registro nao marcado como excluido indo para tras no recordset
        $p  = $this->getRecordPointer();
        $nr = count( $this->m_records );
        $lo = 0;
        do {
            if ( $p < 0 ) {
                $p = $this->rowCount() - 1;
                $lo++;
            }
            $r = $this->m_records[ $p ];
            $p--;
        } while( $r->getStatus() != 3 and $lo < 2 );
        $this->setRecordPointer( $p + 1 );
        return ( $lo < 2 );
    }
    
    /**
     * Is the record pointed to by 'parameter' deleted?
     * 
     * The record is considered deleted only if 'parameter' points to a valid record position
     * and the record status in that position is 3 (DELETED).
     * @return boolean true, if the record is deleted; false, otherwise
     * @param integer $pPos the record position to be tested
     */
    function isDeleted( $pPos ) {
    // verifica se o registro que esta na posicao indicada esta deletado
        if ( $this->rowCount() <= $pPos ) {
            return false;
        }
        elseif ( $this->m_status[ $pPos ] == 3 ) {
            return true;
        }
        else {
            return false;
        }
    }
    
    /**
     * Is the CURRENT record deleted?
     * 
     * This is a wrap to isDeleted(), testing for the current record.
     * @return boolean true, if current record is deleted; false, otherwise.
     */
    function isCurrentRecordDeleted() {
    // verifica se o registro corrente esta deletado
        return $this->isDeleted( $this->getRecordPointer() );
    }

    /**
     * Appends a record, retrieved from the underlying datasource, to the recordset.
     * 
     * <b>IMPORTANT:</b> the appended record receives the status QUERIED, not NEW. This
     * method is intended to be used by initialization routines. To <b>create</b> a record, one
     * should call insertRecord, not this.
     * @param PGBRecord $pRecord the record to be appended
     */
    function appendRecord( $pRecord ) {
    // acrescenta um registro recuperado da base de dados
        $this->m_records[] = $pRecord;
        $this->m_status[]  = 0;
    }

    /**
     * Appends a NEW record to the recordset.
     * 
     * The record should be new to the underlying datasource, or an error is likely to occur. 
     * Making sure the record is new is the implementor's responsibility.
     * @param PGBRecord $pRecord the new record to be inserted 
     */
    function insertRecord( $pRecord ) {
    // cria um novo registro
        $this->m_records[] = $pRecord;
        $this->m_status[]  = 1;
    }
    
    /**
     * Appends a NEW and BLANK record to the recordset.
     * 
     * Every field is initialised to an empty string. The method calls insertRecord() after performing
     * this initialisation.
     */
    function insertBlank() {
    // cria um novo registro em branco
        $r = new PGBRecord();
        while( list( $p, $nome ) = each( $this->getNames() ) ) {
            $r->appendField( new PGBField( $nome, '' ) );
        }
        $this->insertRecord( $r );
    }

    /**
     * Replaces the record at 'position' by the given record.
     * 
     * Replacement only takes place if 'position' points to a valid record. <b>NO CHECK</b> for 
     * consistency is done. So, making sure the given record adheres to the recordset's structure
     * is up to the implementor. The record position is marked as CHANGED afterwards.
     * @param integer $pPos the record position to be updated
     * @param PGBRecord $pRecord the record that will replace the existing one
     */
    function updateRecord( $pPos, $pRecord ) {
    // atualiza um registro existente, se ele existir; senao, nao faz nada
        if ( isset( $this->m_records[ $pPos ] ) and $pRecord->getStatus() >= 2 ) {
            //print '<pre> atualizando ' . $pPos . '</pre>';
            $this->m_records[ $pPos ] = $pRecord;
            if ( $this->m_status[ $pPos ] == 0 ) {
                $this->m_status[ $pPos ]  = 2;
            }
        }
    }

    /**
     * Marks the record at 'position' as "deleted".
     * @param integer $pPos the record position to be deleted
     */
    function deleteRecord( $pPos ) {
    // marca um registro como excluido, se ele existir; senao, nao faz nada
        //print 'excluindo registro ' . $pPos . '<br>';
        if ( isset( $this->m_records[ $pPos ] ) ) {
            $this->m_status[ $pPos ]  = 3;
            //print 'excluido registro ' . $pPos . '<br>';
        }
    }

    /**
     * Getter for all the records in the recordset.
     * @return array the records
     */
    function &getRecords() {
    // retorna todo o array de PGBRecord's
        return $this->m_records;
    }

    /**
     * Getter for the record at 'position'.
     * @return PGBRecord the wanted record
     * @param integer $pPos the record position to be retrieved
     */
    function &getRecord( $pPos ) {
    // retorna o PGBRecord apontado por pPos, se existir
        if ( isset( $this->m_records[ $pPos ] ) ) {
            $resp = &$this->m_records[ $pPos ];
            //print '<pre> getRecord: ' . $resp . '</pre>';
            if ( count( $this->getSlaveDefinitions() ) > 0 ) {
                $resp->makeSlavesFromSlaveDefs( $this->getSlaveDefinitions() );
            }
            else {
                $resp->makeSlaves();
            }
            //$this->m_records[ $pPos ] = $resp;
            return $resp;
        }
        else {
            //print '<pre>' . count( $this->m_records ) . '</pre>';
            return NULL;
        }
    }
    
    /**
     * Getter for the current record position.
     * @return integer the current record position
     */
    function getRecordPointer() {
    // retorna a posicao relativa do registro corrente
        return $this->m_rp;
    }
    
    /**
     * Setter for the current record position.
     * 
     * The record position is modified only if 'parameter' points to a valid record.
     * @param integer $prp the new record position
     */
    function setRecordPointer( $prp ) {
    // reposiciona o recordset, se possivel
        if ( $prp < count( $this->m_records ) and $prp >= 0 ) {
            $this->m_rp = $prp;
        }
    }
    
    /**
     * Getter for the record at the current record position.
     * @return PGBRecord the current record
     */
    function &getCurrentRecord() {
    // retorna o PGBRecord corrente do recordset
        return $this->getRecord( $this->m_rp );
    }
    
    /**
     * Replaces the current record by the given record.
     * 
     * <b>ATTENTION:</b> this is a wrapper for updateRecord(); so, the restrictions and
     * notes stated there also stand here.
     * @param PGBRecord $pRecord the new record
     */
    function updateCurrentRecord( $pRecord ) {
    // atualiza o registro corrente, substituindo-o pelo PGBRecord dado
        $this->updateRecord( $this->m_rp, $pRecord );
    }

    /**
     * Makes the first record the current record.
     */
    function firstRecord() {
    // reposiciona o ponteiro de registros no topo do recordset
        $this->m_rp = 0;
    }
    
    /**
     * Makes the last record the current record.
     */
    function lastRecord() {
    // reposiciona o ponteiro de registros no fim do recordset
        $this->m_rp = $this->rowCount() - 1;
    }
    
    /**
     * Increments the record pointer.
     * 
     * If this would position the recordset after the last record, goes back to the first
     * record (the recordset is circular).
     */
    function nextRecord() {
    // posiciona-se no registro seguinte, se houver
        if ( $this->m_rp < count( $this->m_records ) ) {
            $this->m_rp++;
        }
        else {
            $this->firstRecord();
        }
    }
    
    /**
     * Decrements the record pointer.
     * 
     * If this would position the recordset before the first record, goes to the last
     * record (the recordset is circular).
     */
    function previousRecord() {
    // posiciona-se no registro anterior, se houver
        if ( $this->m_rp > 0 ) {
            $this->m_rp--;
        }
        else {
            $this->lastRecord();
        }
    }

    /**
     * Getter for the number of records in the recordset.
     * @return integer the record count
     */
    function rowCount() {
    // retorna a quantidade de registros
        return count( $this->getRecords() );
    }

    /**
     * Getter for the number of <b>fields</b> in each record.
     * @return integer the column count
     */
    function columnCount() {
    // retorna a quantidade de colunas
        return count( $this->getNames() );
    }

    /**
     * Getter for the whole metadata array.
     * @return array the recordset metadata
     */
    function getMetadata() {
    // retorna o array completo de meta dados
        return $this->m_info;
    }

    /**
     * Setter for the metadata array.
     * 
     * This is intended to be used only by other methods and objects in this framework. So,
     * it's up to the caller making sure the given metadata adheres to the stated standard.
     * @param array $pInfo new metadata
     */
    function setMetadata( $pInfo ) {
    // seta o array completo de meta dados, em seu formato convencional
        $this->m_info = $pInfo;
    }

    /**
     * Getter for the names part of the recordset's metadata.
     * @return array the recordset's field names
     */
    function getNames() {
    // retorna os nomes das colunas do recordset
        return $this->m_info['name'];
    }

    /**
     * Getter for the types part of the recordset's metadata.
     * @return array the recordset's field types
     */
    function getTypes() {
    // retorna os tipos das colunas do recordset
        return $this->m_info['type'];
    }
    
    /**
     * Getter for the type of the given column.
     * @return string the column type (or NULL, if the column identifier is invalid) 
     * @param mixed $pCol the column name or position
     */
    function getType( $pCol ) {
    // retorna o tipo da coluna cuja posicao ou nome foi dado
        if ( isset( $this->m_info['type'][$pCol] ) ) {
            return $this->m_info['type'][$pCol];
        }
        else {
            $p = $this->getPosition( $pCol );
            if ( $p == NULL ) {
                return NULL;
            }
            else {
                return $this->m_info['type'][$p];
            }
        }
    }

    /**
     * Getter for the name of the column at 'position'.
     * @return string the column name (or NULL, if 'position' points to an invalid column)
     * @param integer $pPos the column position
     */
    function getName( $pPos ) {
    // retorna o nome da coluna cuja posicao e dada
        if ( isset( $this->m_info['name'][$pPos] ) ) {
            return $this->m_info['name'][$pPos];
        }
        else {
            return NULL;
        }
    }
    
    /**
     * Getter for the position of the column pointed to by 'name'.
     * @return integer the column position (or -1, if 'name' is invalid or non-existent)
     * @param string $pNome the column name
     */
    function getPosition( $pNome ) {
    // retorna o numero de ordem (posicao) da coluna cujo nome foi passado (-1 se nao achou)
        $n = $this->getNames();
        $r = -1;
        reset( $n );
        while( list( $k, $c ) = each( $n ) ) {
            if ( $c == $pNome ) {
                $r = $k;
                break;
            }
        }
        return $r;
    }

    /**
     * Getter for the recordset's primary key definition.
     * @return PGBPrimaryKey the recordset's PK
     */
    function &getPk() {
    // retorna a definicao da PK
        return $this->m_pk;
    }

    /**
     * Setter for the recordset's primary key definition.
     * 
     * The PK definition is set to the given one only if <b>ALL</b> columns in this PK
     * exist in the recordset. If not, nothing is done.
     * @param PGBPrimaryKey $pPk the new PK definition
     */
    function setPk( $pPk ) {
    // seta a PK a partir da PGBPrimaryKey dada, desde TODOS OS CAMPOS EXISTENTES NELA
    //  EXISTAM no recordset
        $ok = true;
        $fd = $pPk->getDefinition();
        while( list( $k, $pPkName ) = each( $fd ) ) {
            if ( $this->getPosition( $pPkName ) < 0 ) {
                $ok = false;
                break;
            }
        }
        if ( $ok ) {
            $this->m_pk = $pPk;
        }
    }

    /**
     * Getter for all the slave definitions.
     * @return array the slave definitions
     */
    function &getSlaveDefinitions() {
    // retorna o array de definicoes de escravos
        reset( $this->m_slavedefs );
        return $this->m_slavedefs;
    }
    
    /**
     * Setter for all the slave definitions.
     * 
     * Only sets the slave definitions if <b>ALL</b> array elements are valid slave
     * definitions. It's the programmer's responsibility making sure they are consistent
     * with the records in the recordset.
     * @param array $pSdefs the slave definitions
     */
    function setSlaveDefinitions( $pSdefs ) {
    // armazena o array com PGBSlaveDefinitions
    	$ok = true;
    	reset( $pSdefs );
    	while( list( $i, $sd ) = each( $pSdefs ) ) {
    		if ( ! is_a( $sd, 'PGBSlaveDefinition' ) ) {
    			$ok = false;
    			break;
    		}
    	}
    	if ( $ok ) {
        	$this->m_slavedefs = $pSdefs;
    	}
    }
    
    /**
     * Getter for the slave definition pointed to by 'name'.
     * @return PGBSlaveDefinition the wanted slave definition (or NULL, if non-existent)
     * @param string $pName the slave definition name
     */
    function &getSlaveDefinition( $pName ) {
    // retorna a slave definition cujo nome eh o dado
        $sds = &$this->getSlaveDefinitions();
        $r   = NULL;
        reset( $sds );
        while( list( $k, $sd ) = each( $sds ) ) {
            if ( $sd->getName() == $pName ) {
                $r = &$sd;
                break;
            }
        }
        return $r;
    }
    
    /**
     * Getter for the master keys.
     * 
     * Only makes sense for slave recordsets.
     * @return array the master keys (fields that link this recordset to the master)
     */
    function getMasterKeys() {
    // retorna o array de master keys
        return $this->m_masterkeys;
    }
    
    /**
     * Setter for the master keys.
     * 
     * Only makes sense for slave recordsets.
     * @param array $p_mkeys the master key values
     */
    function setMasterKeys( $p_mkeys ) {
    // seta o array de master keys
        $this->m_masterkeys = $p_mkeys;
    }
    
    /**
     * Updates data in the recordset from data in the given PGBDataBlock.
     * 
     * This is part of the framework's data persistency mechanism backbone. This method is
     * used to transfer data, entered by a user through a visual page, to the recordset,
     * after it's been transferred from the REQUEST to the data block.
     * @param PGBDataBlock &$pDB the data block to be used
     */
    function updateFromDataBlock( &$pDB ) {
        if ( !is_a($pDB, 'PGBDataBlock') ) {
            //print '<pre> sem posicao </pre>';
            return;
        }
        $rpoi  = $this->getRecordPointer();
        $nomes = $this->getNames();
        $rpos  = $pDB->getPosArray();
        reset( $rpos );
        //var_dump( $rpos );
        while( list( $bp, $rp ) = each( $rpos ) ) {
            //print '<pre>pos ' . $rp . '</pre>';
            $r = &$this->getRecord( $rp );
            if ( !is_a( $r, 'PGBRecord' ) ) {
                //print '<pre> nao eh registro </pre>';
                break;
            }
            //print '<pre>' . $r . '</pre>';
            reset( $nomes );
            while( list( $k2, $nome ) = each( $nomes ) ) {
                $f = &$pDB->getBlockField( $nome, $bp );
                if ( !is_null( $f ) ) {
                    //print '.. ' . $f->getName() . ' = ' . $f->getValue() . '<br>';
                    $r->updateField( $f );
                }
            }
            $this->updateRecord( $rp, $r );
        }
        $this->setRecordPointer( $rpoi );
    }

}
?>
Return current item: Piragibe