TNT 0 Denunciar post Postado Março 14, 2011 Olá a todos. Recebi um pedido que fiquei em dúvida quanto à viabilidade do projeto. Gostaria de uma opinião de vocês sobre o assunto. O cliente me solicitou o desenvolvimento de um sistema de log, que armazenaria todas as operações efetuadas por um conjunto de usuários. Ou seja, no momento que um administrador do site cadastrasse, deletasse ou mesmo modificasse os dados de algum usuário, o sistema registraria a operação no log. Cadastrar e deletar entendo como mais trivial. O problema é quanto à modificação. Existe algum meio prático de armazenar quais dos campos de uma tabela tiveram seus dados modificados? Levando para um exemplo prático: em um formulário que contenha nome e CPF de uma pessoa, se o administrador modifica apenas o CPF, teria como eu armazenar esta informação no log, com uma mensagem no estilo "O administrador modificou o CPF"? Obrigado e até mais. Compartilhar este post Link para o post Compartilhar em outros sites
Periscuelo 20 Denunciar post Postado Março 15, 2011 Amigo TNT o meio mais pratico para você resolver isso seria ao invés de armazenar campos, armazenar a query no LOG ex. campos da tabela log: usuario, data, acao, query Dai você vai poder armazenar cada passo do usuário e saber de quebra qual query foi executada. Assim quando precisar saber quem fez x alteração basta procurar por ela no log. ;) Abraços. Compartilhar este post Link para o post Compartilhar em outros sites
TNT 0 Denunciar post Postado Março 16, 2011 Boa, não havia pensado nisso e gostei bastante da idéia. Só existe um problema, que seria identificar qual o campo foi modificado dentre os existentes na query. No meu exemplo, em um formulário que contém nome e CPF, armazenando a query inteira eu não saberia qual dos dois campos foi modificado não? Ou eu que não entendi bem? Compartilhar este post Link para o post Compartilhar em outros sites
jcalebe 0 Denunciar post Postado Março 16, 2011 Antes de executar a query que modifica, você recebe o dado atual do banco e guarda numa variável. Depois, executa a query de atualização do dado e cria uma segunda variável que tem os novos dados. Use uma função que compare as duas variáveis geradas, gerando uma só variável e depois salve no banco. Acho que a função strcmp() pode ajudar. B) Compartilhar este post Link para o post Compartilhar em outros sites
Periscuelo 20 Denunciar post Postado Março 17, 2011 Talvez eu não tenha entendido bem. Mas pelo que sei você vai precisar saber qual campo foi alterado caso alguém lhe pergunte não? Ex: Chefe: Quem foi o f.... que mudou o nome do cliente? Você: Quando foi a ultima vez que o nome estava correto e qual era o nome? Chefe: Até ontem estava Lucas Silva Castro e hoje fui ver e esta aparecendo Lucas S.C. Você: Um minuto chefe. Abre o arquivo de log de ontem e hoje e procura queries que contenham Lucas S.C ao encontrar verifica o usuário que fez a alteração e reporta ao chefe. Não seria essa a utilidade??? Abraços. Compartilhar este post Link para o post Compartilhar em outros sites
jcalebe 0 Denunciar post Postado Março 17, 2011 Nesse caso, além de comparar as duas querys, ele também vai pegar algumas outras coisas, como data, hora, IP da máquina e um cookie ou session, sei lá, com o nome do funcionário. Aí o chefe não vai ter dúvidas de "quem foi o f.... que mudou o nome do cliente". veja a ordem que proponho: Receber a query atual e guardar em variável Pegar a query que faz a modificação e guardar pegar data, hora, IP, cookie, session... comparar as duas primeiras variáveis e deixar apenas a modificação juntar tudo em uma variável, ou array (serialize) salvar tudo em uma tabela. Abraços. :skull: :ninja: Compartilhar este post Link para o post Compartilhar em outros sites
TNT 0 Denunciar post Postado Março 17, 2011 Certo, a idéia é esta mesma, mas com um detalhe: o próprio "chefe" quer ter acesso às mensagens de log, indicando qual o campo modificado. E para ele, que é leigo em programação, não seria "amigável" que a mensagem simplesmente fosse a query, entendem? Então eu gostaria que fosse uma mensagem no estilo "f.... modificou o nome do associado de 'Lucas Silva Castro' para 'Lucas S.C.'". Mas para isto eu precisaria descobrir qual campo exatamente foi modificado. A função strcmp acho que não se aplicaria neste caso, já que retorna apenas a igualdade ou não de duas strings.. O que eu precisaria mesmo é de alguma maneira que me fizesse descobrir qual o campo modificado e, até agora, não consigo pensar em outra solução que não seja comparando campo a campo. Problema é que eu acho isto pouco eficiente.. Compartilhar este post Link para o post Compartilhar em outros sites
Periscuelo 20 Denunciar post Postado Março 17, 2011 Na verdade não tem segredo amigo TNT. Ele quer saber quem modificou X campo não? Basta você retornar para ele a primeira ocorrencia da tabela logs onde o nome foi alterado para Lucas S. C. fazendo a pesquisa pela query. Se ele quiser saber quem mudou o endereço faça a pesquisa pelo endereço novo. A primeira ocorrencia de update com o endereço novo já traz todos os dados de quem fez. Pegou a idéia?? ;) Fica bem mais fácil se o log for uma tabela no banco de dados. Mas se desejar fazer em arquivo texto talvez o link abaixo lhe ajude na hora de pesquisar por um valor alterado. http://phpbrasil.com/phorum/read.php?1,21488 Abraços. Compartilhar este post Link para o post Compartilhar em outros sites
TNT 0 Denunciar post Postado Março 18, 2011 Até entendi o que você quer dizer, acho, mas para o que eu pretendo creio que não funcione.. Veja bem, no formato que você está sugerindo, teria que ter um campo para que ele pesquisasse nos logs pela modificação do nome Lucas S.C., certo? Mas o que eu preciso não é exatamente isto, e sim fazer uma página com o formato parecido com isto: - 14h30min: "Admin 1" modificou o nome de "Usuario 1" para "Usuaaaario 1". - 14h35min: "Admin 2" modificou o CPF de "Usuario 1" para "111.111.111-11". Ou seja, uma página com todas as movimentações do sistema e não um sistema de busca que gere resultados. Agora pense na seguinte query que dou como exemplo: "UPDATE usuarios SET nome = '$nome', cpf = '$cpf' WHERE id = '$id'" Se eu salvar uma query nesse estilo, não tenho como gerar uma página com a definição que pretendo, concorda? Até por que simplesmente executando esta query não saberei exatamente quais os campos estarão sendo modificados. A única alternativa que vejo é passar o que possuo previamente no banco para variáveis e comparar com o que estou inserindo. Não sei se consegui me expressar bem, nem se estou demorando a entender uma idéia óbvia, mas enfim.. obrigado pela ajuda. Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 18, 2011 Você pode utilizar Command para fazer isso: Command: Padrão de comportamento Participantes: Command: Declara uma interface para executar uma operação. ConcreteCommand: Cria um vinculo entre o Receiver e uma ação e implementa a interface do Command para chamar a ação apropriada no Receiver. Client: Cria o ConcreteCommand e define seu Receiver. Invoker: Invoca a ação do Command. Receiver: Sabe como executar a ação, qualquer um que souber como executar determinada ação pode atuar como Receiver. <?php class User { private $id; private $name; public function __construct( $id = null , $name = null ) { $this->setId( $id ); $this->setName( $name ); } public function delete() { echo 'Apagando usuário' , PHP_EOL; //DELETE FROM `User` WHERE `id`=:id; } public function insert() { echo 'Criando usuário' , PHP_EOL; //INSERT INTO `User`(`name`) VALUES(:name); } public function update() { echo 'Atualizando usuário' , PHP_EOL; //UPDATE `User` SET `name`=:name WHERE `id`=:id; } public function getId() { return $this->id; } public function getName() { return $this->name; } public function setId( $id ) { $this->id = $id; } public function setName( $name ) { $this->name = $name; } } A classe User será nossa entidade, mas também será nosso Receiver. <?php interface Command { public function execute(); } <?php abstract class AbstractCommand implements Command { protected $user; final public function __construct( User $user ) { $this->user = $user; } } AbstractCommand é apenas para reutilizar código, a implementação do método execute() é do ConcreteCommand: <?php class CreateCommand extends AbstractCommand { public function execute() { $this->user->insert(); } } <?php class UpdateCommand extends AbstractCommand { public function execute() { $this->user->update(); } } <?php] class DeleteCommand extends AbstractCommand { public function execute() { $this->user->delete(); } } <?php class Logger { public function log( Command $command ) { printf( "Gravando log para o comando %s\n" , get_class( $command ) ); //INSERT INTO `Log` ... } } Um Invoker "besta": <?php class UserInvoker { private $logger; public function __construct() { $this->logger = new Logger(); } public function create( Command $createCommand ) { $this->doLog( $createCommand ); $createCommand->execute(); } public function update( Command $updateCommand ) { $this->doLog( $updateCommand ); $updateCommand->execute(); } public function delete( Command $deleteCommand ) { $this->doLog( $deleteCommand ); $deleteCommand->execute(); } private function doLog( Command $command ) { $this->logger->log( $command ); } } E um Client "mais besta" ainda: <?php class UserModel { public function test() { $user = new User( 1 , 'Neto' ); $invoker = new UserInvoker(); $invoker->create( new CreateCommand( $user ) ); $user->setName( 'João Batista Neto' ); $invoker->update( new UpdateCommand( $user ) ); $invoker->delete( new DeleteCommand( $user ) ); } } Usando isso ai: <?php $userModel = new UserModel(); $userModel->test(); Saída: Gravando log para o comando CreateCommand Gravando log para o comando UpdateCommand Gravando log para o comando DeleteCommand Ok, mas obviamente seu cliente não quer apenas ver o que está acontecendo, ele quer que, caso alguma coisa errada tenha acontecido, ele possa reverter. E ai ? Uma das consequências de Command é a possibilidade de você poder proporcionar um "rollBack" ou seja, oferecer um "Ctrl + z" para seu cliente. Mas, para oferecer o Ctrl + z para seu cliente, você precisará guardar o estado atual do objeto, antes de executar a ação, para isso usamos outro padrão: Atualizando nossos participantes: <?php /** * Interface para um comando que pode ser executado mas * que pode ser revertido */ interface Command { /** * Executa o comando */ public function execute(); /** * Reverte o comando */ public function rollBack(); } <?php /** * Caretaker é o cara que cuida dos nossos logs e mantém um * histórico de tudo que aconteceu. */ class Caretaker { private $history = array(); public function store( Command $command , Memento $memento ) { array_unshift( $this->history , array( $command , $memento ) ); printf( "Gravando log para o comando %s\n" , get_class( $command ) ); //INSERT INTO `Log` ... } public function recover( $level = 1 ) { for ( $i = 0 ; isset( $this->history[ $i ] ) && $i < $level ; ++$i ) { $command = $this->history[ $i ][ 0 ]; printf( "Revertendo comando %s\n" , get_class( $command ) ); $command->setMemento( $this->history[ $i ][ 1 ] ); $command->rollBack(); } } } <?php /** * A entidade usuário, ela precisa ser serializável porque * precisaremos guardá-la no banco de dados, caso seja * necessário revertê-la a um estado anterior */ class User implements Serializable { private $id; private $name; public function __construct( $id = null , $name = null ) { $this->setId( $id ); $this->setName( $name ); } public function delete() { //DELETE FROM ... } public function insert() { //INSERT INTO ... } public function update() { //UPDATE ... } public function getId() { return $this->id; } public function getName() { return $this->name; } public function setId( $id ) { $this->id = $id; } public function setName( $name ) { $this->name = $name; } public function serialize() { return serialize( array( 'id' => $this->id , 'name' => $this->name ) ); } public function unserialize( $serialized ) { $data = unserialize( $serialized ); if ( isset( $data[ 'id' ] ) && isset( $data[ 'name' ] ) ) { $this->id = (int) $data[ 'id' ]; $this->name = $data[ 'name' ]; } else { throw new UnexpectedValueException( 'Erro ao deserializar objeto' ); } } } <?php /** * O Memento manterá uma cópia de estado antes de alguma coisa * ser feita */ class Memento { private $state; public function __construct( $state ) { $this->setState( $state ); } public function setState( $state ) { $this->state = $state; } public function getState() { return $this->state; } } <?php /** * O Originator é também a base para nossos comandos, * com ele poderemos executar ações, mas também poderemos * desfazer "khdas" */ abstract class Originator implements Command { protected $user; final public function __construct( User $user ) { $this->user = $user; } public function createMemento() { return new Memento( $this->user ); } public function setMemento( Memento $memento ) { $this->user = $memento->getState(); } } <?php /** * Grava uma entidade no banco, mas também apaga caso necessário */ class CreateCommand extends Originator { public function execute() { $this->user->insert(); } public function rollBack() { $this->user->delete(); } } <?php /** * Atualiza uma entidade no banco, mas também reverte a atualização */ class UpdateCommand extends Originator { public function execute() { $this->user->update(); } public function rollBack() { $this->user->update(); } } <?php /** * Apaga uma entidade do banco, mas também grava novamente se necessário */ class DeleteCommand extends Originator { public function execute() { $this->user->delete(); } public function rollBack() { $this->user->insert(); } } <?php /** * O cara que será acionado por nosso client para efetuar as ações */ class UserInvoker { private $caretaker; public function __construct() { $this->caretaker = new Caretaker(); } public function execute( Command $createCommand ) { $this->register( $createCommand ); $createCommand->execute(); } public function rollBack( $level = 1 ) { $this->caretaker->recover( $level ); } private function register( Command $command ) { $this->caretaker->store( $command , $command->createMemento() ); } } <?php /** * Nosso Client */ class UserModel { private $invoker; public function __construct() { $this->invoker = new UserInvoker(); } public function test() { $user = new User( 1 , 'Neto' ); $this->invoker->execute( new CreateCommand( $user ) ); $user->setName( 'João Batista Neto' ); $this->invoker->execute( new UpdateCommand( $user ) ); $this->invoker->execute( new DeleteCommand( $user ) ); } public function opz( $level = 1 ) { $this->invoker->rollBack( $level ); } } Usando: <?php $userModel = new UserModel(); $userModel->test(); $userModel->opz( 1 ); //Revertendo apenas o comando Delete Saída: Gravando log para o comando CreateCommand Gravando log para o comando UpdateCommand Gravando log para o comando DeleteCommand Revertendo comando DeleteCommand ;) Compartilhar este post Link para o post Compartilhar em outros sites