<?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 © 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 );
}
}
?>