Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Marcelo Garbin

Classe CRUD com PDO

Recommended Posts

Buenas pessoal, tudo belezinha? :joia:

 

Seguinte, comecei a desenvolver uma classe para usar em futuros trabalhos, como é a primeira vez que trabalho com Classe e PDO gostaria da opinião de vocês como posso melhorar o meu código e também verificar os erros do mesmo.

 

Nessa classe tem uma função que grava logs após usar as funções: Insert, Update, Delete.

 

Alguém disposto a me ajudar??? :clover:

 

Segue as classes:

 

Classe PDO (Connect.inc.php)

<?php

class BD{
	private $ConnHost;
	private $ConnUser;
	private $ConnPass;
	private $ConnDb;
	private $ConnDriver;

	function __construct(){
		$this->ConnDriver = "mysql";
		$this->ConnHost   = "localhost";
		$this->ConnUser   = "root";
		$this->ConnPass   = "root";
		$this->ConnDb     = "testes";		
	}	

	function Connect(){
		$conn = NULL;
		try{
			$conn = new PDO($this->ConnDriver.":host=".$this->ConnHost.";dbname=".$this->ConnDb,$this->ConnUser,$this->ConnPass);
		}catch (PDOException $e){
			$e->getMessage();
			echo "Atenção! Ocorreu um erro ao tentar se conectar ao banco de dados.<br />Caso o erro persistir contate o administrador do sistema!";
			exit();
		}
		return $conn;
	}
}

?>

 

Classe CRUD (CRUD.class.php)

 

<?php 

//Classe para conexao BD
include_once("Connect.inc.php");

/***
 * 
 * 
 * @category   Class for Create, Read, Update and Delete (CRUD)
 * @author     Marcelo Garbin <marcelo.garbin@hotmail.com>
 * @copyright  2013 - Marcelo Garbin
 * @version    1.0
 * 
 * 
 ***/

class CRUD{
	//CRUD 
	private $SQL;
	private $LogSQL;
	private $Table;
	private $TFields;
	private $TValues;
	private $Query;
	//Set table log
	private $TLogs;

	function __construct(){
		$this->SQL 		= NULL;
		$this->LogSQL   = NULL;
		$this->Table    = NULL;
		$this->TFields  = NULL;
		$this->TValues  = NULL;
		$this->Query    = NULL;
		//Set table log
		$this->TLogs    = "logs_system";
	}

	function Insert(){		
		try{	
			//Dados
			$table   = self::GetTable();
			$campos  = self::GetTFields();
			$valores = self::GetTValues();	
			$fields  = NULL;
			$values  = NULL;		
			$FieldValue = array_combine($campos,$valores);
			//Separa campos
			foreach($FieldValue as $field => $value){
				$fields .= $field.', ';
				$values .= $value.', ';
			}	
			//Remove o espaço e virgula final
			$fields = substr($fields, 0, strlen($fields)-2);
			$values = substr($values, 0, strlen($values)-2);

			if(!empty($table) && !empty($fields)){	

				if(!empty($table) && !empty($fields) && !empty($valores)){	
					//Cria objeto e abre conexão
					$pdo = new BD;
					$pdo = $pdo->Connect();
					//Monta SQL para insert
					self::SetSQL("INSERT INTO ".$table." (".str_replace(':','', $fields).")"." VALUES "."(".$fields.");");
					//Prepara Insert
					$stmt = $pdo->prepare(self::GetSQL());
					//Trata entrada de dados
					foreach($FieldValue as $field => $value){
						$stmt->bindValue($field, $value);	
					}	
					//Executa o metodo
				    if($stmt->execute() && $stmt->rowCount() > 0){
				    	//Monta SQL para Log
				    	self::SetLogSQL("INSERT INTO ".$table." (".str_replace(':','', $fields).")"." VALUES "."(".$values.");");	
				    	//Seta o log e sua acao			    	
				    	self::SetLogs('INSERT');
				        return true;
				    }else{
				     	return false;
				    }
				}else{
					echo '<br /><strong>Erro: </strong>Faltam complementos para executar a ação!<br />';
				}
			}
		}
		catch(PDOException $e){
		   echo $e->getMessage();
		}
		$pdo     = NULL;
		$table   = NULL;
		$campos  = NULL;
		$valores = NULL;
		$fields  = NULL;
		$values  = NULL;
	}

	function Update($id){		
		try{	
			//Dados
			$table   = self::GetTable();
			$campos  = self::GetTFields();
			$valores = self::GetTValues();	
			$fields  = NULL;
			$values  = NULL;		
			$FieldValue = array_combine($campos,$valores);
			//Separa campos
			foreach($FieldValue as $field => $value){
				$field   = str_replace(':','', $field);
				$fields .= $field.'=:'.$field.', ';
				$values .= $field.'=\''.$value.'\', ';
			}	
			//Remove o espaço e virgula final
			$fields = substr($fields, 0, strlen($fields)-2);
			$values = substr($values, 0, strlen($values)-2);

			if(!empty($table) && !empty($fields)){	

				if(!empty($table) && !empty($fields) && !empty($valores)){	
					//Cria objeto e abre conexão
					$pdo = new BD;
					$pdo = $pdo->Connect();
					//Verifica se id existe no BD
					$stmt = $pdo->prepare("SELECT * FROM ".$table." WHERE id=:id");
					$stmt->bindValue(":id", $id, PDO::PARAM_INT);
					$stmt->execute();
					if($stmt->rowCount() > 0){
						//Monta SQL para insert
						self::SetSQL("UPDATE ".$table." SET ".$fields." WHERE id=:id;");
						//Prepara Insert
						$stmt = $pdo->prepare(self::GetSQL());
						//Trata entrada de dados
						foreach($FieldValue as $field => $value){
							$stmt->bindValue($field, $value);	
						}	
						$stmt->bindValue(':id', $id, PDO::PARAM_INT);
						//Executa o metodo
					    if($stmt->execute() && $stmt->rowCount() > 0){	
					    	//Monta SQL para Log
					    	self::SetLogSQL("UPDATE ".$table." SET ".$values." WHERE id=".$id.";");
					    	//Seta o log e sua acao			    	
					    	self::SetLogs('UPDATE');
					        return true;
					    }else{
					     	return false;
					    }
					}else{
						echo '<br /><strong>Error: </strong>Record not found in our database!<br />';
					}
				}else{
					echo '<br /><strong>Error: </strong>Insufficient ons to perform the action!<br />';
				}
			}
		}
		catch(PDOException $e){
		   echo $e->getMessage();
		}
		$pdo     = NULL;
		$table   = NULL;
		$campos  = NULL;
		$valores = NULL;
		$fields  = NULL;
		$values  = NULL;
	}

	function Delete($id){		
		try{	
			//Dados
			$table  = self::GetTable();	

			if(!empty($id)){	
					//Cria objeto e abre conexão
					$pdo = new BD;
					$pdo = $pdo->Connect();
					//Verifica se id existe no BD
					$stmt = $pdo->prepare("SELECT * FROM ".$table." WHERE id=:id");
					$stmt->bindValue(":id", $id, PDO::PARAM_INT);
					$stmt->execute();
					if($stmt->rowCount() > 0){
						//Monta SQL para insert
						self::SetSQL("DELETE FROM ".$table." WHERE id=:id;");
						//Prepara Insert
						$stmt = $pdo->prepare(self::GetSQL());
						//Trata entrada de dados
						$stmt->bindValue(':id', $id);		
						//Executa o metodo
					    if($stmt->execute() && $stmt->rowCount() > 0){	
					    	//Monta SQL para Log
					    	self::SetLogSQL("DELETE FROM ".$table." WHERE id=".$id.";");
					    	//Seta o log e sua acao			    	
					    	self::SetLogs('DELETE');
					        return true;
					    }else{
					     	return false;
					    }
				    }else{
				    	echo '<br /><strong>Erro: </strong>Registro não encontrado no banco de dados!<br />';
				    }
			}else{
				echo '<br /><strong>Erro: </strong>Faltam complementos para executar a ação!<br />';
			}	
		}
		catch(PDOException $e){
		   echo $e->getMessage();
		}
		$pdo     = NULL;
		$table   = NULL;
		$id  	 = NULL;
	}

	function Select($query){
		try{
			//Dados
			self::SetQuery($query);

			if(!empty($query)){
				//Cria objeto e abre conexão
				$pdo = new BD;
				$pdo = $pdo->Connect();
				//Verifica se query e valida
				if($stmt = $pdo->query(self::GetQuery())){
					//Retorna todos os valores ref. a query
					$res = $stmt->fetchAll(PDO::FETCH_ASSOC);
					return $res;
				}else{
					echo '<br /><p><strong>Error: </strong>Invalid Query, check the SQL code!<br /></p>';
					return false;
				}				
			}
		}catch(Exception $e){
			echo $e->getMessage();
		}
		$pdo = NULL;		
	}
	
	function SetLogs($acao){		
		try{
			//Cria objeto e abre conexão
			$pdo = new BD;
			$pdo = $pdo->Connect();
			//Prepata insert
		    $stmt = $pdo->prepare("INSERT INTO ".$this->TLogs."(ip_remote, login, password, action, action_sql, date_time) VALUES (:ip_remote, :login, :password, :action, :action_sql, NOW())");
		    //Trata entrada de dados
		    $stmt->bindValue(':ip_remote', $_SERVER['REMOTE_ADDR']);
		    $stmt->bindValue(':login', $login=null);
		    $stmt->bindValue(':password', $senha=null);
		    $stmt->bindValue(':action', $acao);
		    $stmt->bindValue(':action_sql', self::GetLogSQL());
		    //Executa o metodo
			$stmt->execute();
		}
		catch(PDOException $e){
		   echo $e->getMessage();
		}
		$pdo = NULL;
	}


	/* ////// */
	/* Set's */
	/* //// */
	function SetTable($table = NULL){
		$this->Table = $table;
	}

	function SetTFields($campos = NULL){
		$this->TFields = $campos;
	}

	function SetTValues($valores = NULL){
		$this->TValues = filter_var_array(array_map("htmlspecialchars",$valores), FILTER_SANITIZE_STRING);
	}

	function SetSQL($sql = NULL){
		$this->SQL = $sql;
	}

	function SetLogSQL($sql = NULL){
		$this->LogSQL = $sql;
	}

	function SetQuery($query = NULL){
		$this->Query = $query;
	}

	/* ////// */
	/* Get's */
	/* //// */
	function GetTable(){
		return $this->Table;
	}

	function GetTFields(){
		return $this->TFields;
	}

	function GetTValues(){
		return $this->TValues;
	}

	function GetSQL(){
		return $this->SQL;
	}

	function GetLogSQL(){
		return $this->LogSQL;
	}

	function GetQuery(){
		return $this->Query;
	}
}

?>

 

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Let's go!

 

 

	function __construct(){
		$this->ConnDriver = "mysql";
		$this->ConnHost   = "localhost";
		$this->ConnUser   = "root";
		$this->ConnPass   = "root";
		$this->ConnDb     = "testes";		
	}

 

Configuração não deve ser posta diretamente numa classe. Há dois principais problemas: menor flexibilidade e você torna difícil uma coisa tão banal, nesse caso: conexões múltiplas. Além disso, considere ver o Open/Closed Principle.

 

- Passar por parâmetros seria uma boa solução.

 

 

	function Connect(){
		$conn = NULL;
		try{
			$conn = new PDO($this->ConnDriver.":host=".$this->ConnHost.";dbname=".$this->ConnDb,$this->ConnUser,$this->ConnPass);
		}catch (PDOException $e){
			$e->getMessage();
			echo "Atenção! Ocorreu um erro ao tentar se conectar ao banco de dados.<br />Caso o erro persistir contate o administrador do sistema!";
			exit();
		}
		return $conn;
	}
}

 

- Como conexão é um passo inevitável e simples, poderia estar no próprio construtor.

- Outro problema é o acoplamento ao PDO, tente usar Dependency Injection.

- Evite exit e echo. E se eu quiser simplesmente ignorar esse erro ou traduzí-lo para outra língua? Simplesmente deixe o PDO lançar a exception dele e trate-as com um set_exception_handler.

 

 

?>

 

Como o arquivo inteiro é somente PHP, a tag de fechamento não deve ser utilizada.

 

 

include_once("Connect.inc.php");

 

Autoloading, namespacing e PSR-0.

 

 

		$this->SQL 		= NULL;
		$this->LogSQL   = NULL;
		$this->Table    = NULL;
		$this->TFields  = NULL;
		$this->TValues  = NULL;
		$this->Query    = NULL;

 

O valor padrão das propriedades é null. Você não precisa gastar processamento e tamanho de arquivo para dizer uma "redundância redundante de uma forma redundante".

 

 

		$this->TLogs    = "logs_system";

 

O mesmo problema do construtor da outra classe. Além disso, não é responsabilidade dessa classe fazer logs. Uma solução boa seria usar eventos/hooks/signals, usando um Observer ou Mediator.

 

 

	function Insert(){		
		try{	
			//Dados
			$table   = self::GetTable();
			$campos  = self::GetTFields();
			$valores = self::GetTValues();	
			$fields  = NULL;
			$values  = NULL;		
			$FieldValue = array_combine($campos,$valores);
			//Separa campos
			foreach($FieldValue as $field => $value){
				$fields .= $field.', ';
				$values .= $value.', ';
			}	
			//Remove o espaço e virgula final
			$fields = substr($fields, 0, strlen($fields)-2);
			$values = substr($values, 0, strlen($values)-2);

			if(!empty($table) && !empty($fields)){	

				if(!empty($table) && !empty($fields) && !empty($valores)){	
					//Cria objeto e abre conexão
					$pdo = new BD;
					$pdo = $pdo->Connect();
					//Monta SQL para insert
					self::SetSQL("INSERT INTO ".$table." (".str_replace(':','', $fields).")"." VALUES "."(".$fields.");");
					//Prepara Insert
					$stmt = $pdo->prepare(self::GetSQL());
					//Trata entrada de dados
					foreach($FieldValue as $field => $value){
						$stmt->bindValue($field, $value);	
					}	
					//Executa o metodo
				    if($stmt->execute() && $stmt->rowCount() > 0){
				    	//Monta SQL para Log
				    	self::SetLogSQL("INSERT INTO ".$table." (".str_replace(':','', $fields).")"." VALUES "."(".$values.");");	
				    	//Seta o log e sua acao			    	
				    	self::SetLogs('INSERT');
				        return true;
				    }else{
				     	return false;
				    }
				}else{
					echo '<br /><strong>Erro: </strong>Faltam complementos para executar a ação!<br />';
				}
			}
		}
		catch(PDOException $e){
		   echo $e->getMessage();
		}
		$pdo     = NULL;
		$table   = NULL;
		$campos  = NULL;
		$valores = NULL;
		$fields  = NULL;
		$values  = NULL;
	}

 

Bem, isso vale tanto para o Insert quanto para os outros que são bem parecidos.

 

- O método está enorme e com uma grande complexidade ciclomática, reduza isso.

- Esse try, como comentado anteriormente, é desnecessário.

- Again and again.. você não precisa dizer que o diabo da variável é null, nem precisa de imprimir uma mensagem diretamente para mostrar um erro. Use sempre exceptions e trate no final com um set_exception_handler.

- self:: é para métodos estáticos, use $this-> para métodos de instância. Se tentar rodar esse código em E_STRICT, vai ver que há um erro.

- Não faça um retorno booleano (true/false) quando não há certeza do que isso significa. Return true/false é recomendado para métodos que são uma pergunta (isAuthorized, hasFlag, etc.).

- Não crie uma conexão para cada método executado, isso destrói a performance.

- Evite checagem de empty() quando não é necessário.

- Como o método Connect da classe BD retorna, você acaba por violar a Lei de Demeter se usada dessa forma. Um ponto a ser considerado.

 

Por último, isso:

 

 

//Classe para conexao BD

/***
 * 
 * 
 * @category   Class for Create, Read, Update and Delete (CRUD)
 * @author     Marcelo Garbin <marcelo.garbin@hotmail.com>
 * @copyright  2013 - Marcelo Garbin
 * @version    1.0
 * 
 * 
 ***/

//Set table log
//Dados
//Separa campos
//Remove o espaço e virgula final	
//Cria objeto e abre conexão
//Monta SQL para insert
//Prepara Insert
//Trata entrada de dados
//Executa o metodo
//Monta SQL para Log
//Seta o log e sua acao			    	
/* ////// */
/* Set's */
/* //// */
/* ////// */
/* Get's */
/* //// */

 

Você realmente precisa desses comentários? Por quê?

 

Comentários redundantes poluem o código. Comentários com o propósito de explicar melhor o código acabam poluindo e ainda mascara um código ruim.

 

Os comentários são realmente úteis para documentar a API pública e te ajudar a lembrar de tarefas através de @todo's e @fixme's.

Compartilhar este post


Link para o post
Compartilhar em outros sites

SELECT sem prepare ??
Eu tenho uma dúvida sobre criar um select passando os valores via array e tratar com o bindParam...
Exemplo:

Classe::Read('Database', 'Tabela', 'WHERE', 'OFFSET, 'LIMIT', 'ORDERBY');

 

Queria ter uma ideia de como pegar o WHERE até o ORDERBY e separar cada valor no bindParam do PDO até por que o WHERE pode ter mais de uma condição assim como o ORDERBY.

 

Se pensarmos em algo assim e criarmos creio que seria útil o SELECT fora isso não vejo motivo de usar o PDO.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Se pensarmos em algo assim e criarmos creio que seria útil o SELECT fora isso não vejo motivo de usar o PDO.

A principal utilidade do PDO é quanto à abstração do SGDB que vai ser utilizado. Utilizando PDO você troca de MySQL pra Postgres, pra SQLite, Firebird, MSSQL só mudando o construtor e adaptando as consultas.

Compartilhar este post


Link para o post
Compartilhar em outros sites

A principal utilidade do PDO é quanto à abstração do SGDB que vai ser utilizado. Utilizando PDO você troca de MySQL pra Postgres, pra SQLite, Firebird, MSSQL só mudando o construtor e adaptando as consultas.

 

Entendo... Mas e no sentido de tratar as querys antes de enviar para a base de dados? O Read sem bindParam não teria utilidade, seria enviados injections tranquilamente...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Você não precisa ficar refém do bind. Tem uma outra forma que eu acho bem mais sexy ;)

$stmt = $pdo->prepare('select titulo, conteudo, capa from posts where datapost = :data and author = :autor');

$stmt->execute($_POST);

var_dump($stmt->fetchAll());

Compartilhar este post


Link para o post
Compartilhar em outros sites

Mas e em relação ao crud? E se quiser fazer ele dinâmico como eu disse acima

Classe::Read('Database', 'Tabela', 'WHERE', 'OFFSET, 'LIMIT', 'ORDERBY');

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Mas e em relação ao crud? E se quiser fazer ele dinâmico como eu disse acima


Classe::Read('Database', 'Tabela', 'WHERE', 'OFFSET, 'LIMIT', 'ORDERBY');

 

 

Cara, classe crud só não é um equívoco maior do que estender um Usuário com uma classe DB. A propósito, seguindo a linha de desenvolvimento deste tópico, seria - provavelmente - o próximo passo.

 

O tipo de flexibilidade qu você almeja pode ser alcançado implementando o padrão interpreter

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Enrico, suas dicas são muito boas, porém como sou novato com classes pra mim tudo é um obstacúlo :no: (Sim, pretendo passar por esses obstacúlos, estou pesquisando material e códigos pela web.

 

Teria algum exemplo da classe pdo recebendo por parametro a configuração? Gostei muito dessa ideia e até então nem havia me tocado pelo fato de eu estar focado apenas com mysql, foi muito valida sua colocação.

 

Deletei toda a classe BD e deixei apenas o __construct, isso aqui:

 

<?php
class DB{
	private $DBDriver;
	private $DBHost;
	private $DBUser;
	private $DBPass;
	private $DBName;	

	function __construct($DBDriver, $DBHost, $DBUser, $DBName, $DBPass, $Exception){
		$this->$conn = new PDO($this->DBDriver.":host=".$this->DBHost.";dbname=".$this->DBName,$this->DBUser,$this->DBPass);	
	}
}

Ai para criar o objeto eu fiz o seguinte:

 

<?php

require_once("./includes/Connect.inc.php");
//
$obj = new DB('mysql','localhost','root','root','testes',true);
var_dump($obj);

Bom isso é apenas um teste(mal sucedido) para tentar entender como fazer essa tal conexão hehehe..

 

Dicas ai?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crud é abstrato demais para ser abstraído e não significa banco de dados. Acho que esse é o maior equívoco do código acima.

Não adianta querermos uma solução única, rápida para fazer cruds rápidos, ela não vai existir.

 

 

Entendo... Mas e no sentido de tratar as querys antes de enviar para a base de dados? O Read sem bindParam não teria utilidade, seria enviados injections tranquilamente...

 

CRUD não significa banco de dados, como mencionado. Uma implementação:

 

Nossa entidade usuário:

 

<?php
namespace BeautifulUserCrud;

use RuntimeException;

/**
 * Exception thrown when some validation fails.
 */
class ValidationError extends RuntimeException
{
}

/**
 * Representation of an user.
 */
class User
{
    private $name;
    private $email;
    private $password;

  /**
   * Creates a user.
   * @param string $name
   * @param string $email      
   * @param string $password
   * @param string $repeatedPassword The password the user typed again for security reasons.
   * @throws ValidationError If the name is shorter than 2 characters.
   * @throws ValidationError If the email isn't valid.
   * @throws ValidationError If the password is shorter than 6 characters.
   * @throws ValidationError If the repeated password isn't equal to the first password.
   * @todo Use a validation library.
   */
    public function __construct($name, $email, $password, $repeatedPassword)
    {
        if (strlen($name) < 2) {
            throw new ValidationError('O nome deve conter no mínimo 2 caracteres.');
        }

        if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
            throw new ValidationError('O email informado não é válido.');
        }

        if (strlen($password) < 6) {
            throw new ValidationError('A senha deve conter no mínimo 6 caracteres.');
        }

        if ($password !== $repeatedPassword) {
            throw new ValidationError('As senhas informadas não são iguais.');
        }

        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }

    /**
     * Returns the user name.
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns the user email.
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Returns the user password.
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }
}

 

Nossa interface que serve como contrato para um storage (banco de dados, por exemplo):

 

<?php
namespace BeautifulUserCrud;

use RuntimeException;

/**
 * Exception thrown when an user doesn't exist.
 */
class UserNotFound extends RuntimeException
{
}

/**
 * Contract for storages that will be able to store users.
 */
interface UserDataStorage
{
    /**
     * Inserts an user.
     * @param User $user
     */
    public function insert(User $user);

    /**
     * Updates an existing user.
     * @param int $id
     * @param User $user
     * @throws UserNotFound If there's no user with such id.
     */
    public function update($id, User $user);

    /**
     * Deletes an user.
     * @param int $id
     * @throws UserNotFound If there's no user with such id.
     */
    public function delete($id);

    /**
     * Find all the users.
     * @return User[]
     */
    public function find();

    /**
     * Find an user by his id.
     * @param int $id
     * @throws UserNotFound If there's no user with such id.
     */
    public function findOneById($id);
}

 

A implementação do PDO. É fictícia pelo tempo :(:

<?php
namespace BeautifulUserCrud;

use RuntimeException;

/**
 * Handle user storage using PDO.
 */
class PDOUserDataStorage implements UserDataStorage
{
    private $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    /**
     * {@inheritDoc}
     */
    public function insert(User $user)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function update($id, User $user)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function delete($id)
    {
    }

    /**
     * {@inheritDoc}
     */
    public function find()
    {
    }

    /**
     * {@inheritDoc}
     */
    public function findOneById($id)
    {
    }
}

 

E por último poderíamos ter um controller recebendo um storage no construtor e fazendo todo o trabalho. Acho que deu para entender um pouco de abstração, responsabilidade e para que os comentários realmente servem.

Compartilhar este post


Link para o post
Compartilhar em outros sites

- O método está enorme e com uma grande complexidade ciclomática, reduza isso.

 

O PHP.MD reclama DEMAIS sobre isso mas não ajuda em nada na hora de solucionar. Quando eu pegava no pesado com os códigos, certa vez eu rodei sobre uma rotina de adição de cache e ele reclamou porque aparentemente tinha muitos IFs.

 

Mas o código do método em si estava enxutíssimo, fazia apenas o que deveria ser feito. Esse é um dos problemas mais tretas de resolver.

 

- Esse try, como comentado anteriormente, é desnecessário.

 

Pelo que pude observar, não é desnecessário não. Como ele está usando a PDO e a PDOStatement também dispara PDOExceptions, deve sim utilizar. O erro aí está que o try está muito longe do catch, ele deveria vir imediatamente antes de invocar o PDO::prepare().

 

- Não crie uma conexão para cada método executado, isso destrói a performance.

 

- Como o método Connect da classe BD retorna, você acaba por violar a Lei de Demeter se usada dessa forma. Um ponto a ser considerado.

 

Não sei quem é esse fulano, mas é fácil de resolver esses dois pontos.

 

No método de conexão, ao invés de armazenar o objeto numa variável e retorná-la, você armazena numa propriedade (previamente criada, claro) e, no início do método você verifica se há uma conexão (no caso da PDO basta usar instanceof). Se houver um objeto, retorna a propriedade, se não houver você faz a conexão, popula a dita propriedade e a retorna.

 

Dado o fluxo vertical e crescente, se alguém lá atrás conectou ao banco a propriedade vai estar com o objeto e reinvocar o método de conexão, não vai sobrecarregar o sistema.

Compartilhar este post


Link para o post
Compartilhar em outros sites

No método de conexão, ao invés de armazenar o objeto numa variável e retorná-la, você armazena numa propriedade (previamente criada, claro) e, no início do método você verifica se há uma conexão (no caso da PDO basta usar instanceof). Se houver um objeto, retorna a propriedade, se não houver você faz a conexão, popula a dita propriedade e a retorna.

 

O Registry pode ajudar nisso, não?!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sim e não. O Registry vai permitir a você perpetuar o objeto, a grosso modo, de uma classe pra outra. O caso aí é que isso evita que sejam abertas várias conexões.;

Compartilhar este post


Link para o post
Compartilhar em outros sites

O PHP.MD reclama DEMAIS sobre isso mas não ajuda em nada na hora de solucionar. Quando eu pegava no pesado com os códigos, certa vez eu rodei sobre uma rotina de adição de cache e ele reclamou porque aparentemente tinha muitos IFs.

 

Mas o código do método em si estava enxutíssimo, fazia apenas o que deveria ser feito. Esse é um dos problemas mais tretas de resolver.

 

Eu nem falo do PHP MD, mas de bater o olho dá para ver que a complexidade ciclomática é alta. Para cache, Decorator é um bom pattern que substitui os if's.

 

Não é que seja fácil de resolver, mas há vários if's e complexidades desnecessárias.

 

 

Pelo que pude observar, não é desnecessário não. Como ele está usando a PDO e a PDOStatement também dispara PDOExceptions, deve sim utilizar. O erro aí está que o try está muito longe do catch, ele deveria vir imediatamente antes de invocar o PDO::prepare().

 

Se fosse para ignorar a exception ou lançar uma exception mais específica, aí tudo bem. Mas pegar uma exception para dar um echo + exit....

 

 

Não sei quem é esse fulano, mas é fácil de resolver esses dois pontos.

 

No método de conexão, ao invés de armazenar o objeto numa variável e retorná-la, você armazena numa propriedade (previamente criada, claro) e, no início do método você verifica se há uma conexão (no caso da PDO basta usar instanceof). Se houver um objeto, retorna a propriedade, se não houver você faz a conexão, popula a dita propriedade e a retorna.

 

Dado o fluxo vertical e crescente, se alguém lá atrás conectou ao banco a propriedade vai estar com o objeto e reinvocar o método de conexão, não vai sobrecarregar o sistema.

 

http://en.wikipedia.org/wiki/Law_of_Demeter - É só um nome de homenagem, não sei para quem exatamente. O problema é que, como você disse, o método retorna o objeto e o método é chamado diretamente, gerando acoplamento (é uma "lei" no sentido de que isso sempre acontece, apesar do nome parecer arrogante).

 

Ao invés disso:

 

 

<?php

class BD{
	private $ConnHost;
	private $ConnUser;
	private $ConnPass;
	private $ConnDb;
	private $ConnDriver;

	function __construct(){
		$this->ConnDriver = "mysql";
		$this->ConnHost   = "localhost";
		$this->ConnUser   = "root";
		$this->ConnPass   = "root";
		$this->ConnDb     = "testes";		
	}	

	function Connect(){
		$conn = new PDO($this->ConnDriver.":host=".$this->ConnHost.";dbname=".$this->ConnDb,$this->ConnUser,$this->ConnPass);
		return $conn;
	}
}

class CRUD{
	function Insert(){
        	$pdo = new DB;
                $pdo = $pdo->Connect();
                $pdo->prepare('..');
        }
}

 

Basta usar a PDO e injetar a dependência.

<?php

class CRUD{
        private $pdo;

        function __construct(PDO $pdo){
              $this->pdo = $pdo;
        }

	function Insert(){
        	$this->pdo->prepare('..');
        }
}

$pdo = new PDO('mysql:host=localhost;dbname=testes', 'root', 'root');

$crud = new CRUD($pdo);
// ...

Agora, eliminamos o problema de criar conexão toda hora, o problema do acoplamento, o problema da Lei de Demeter e eliminamos uma classe desnecessária.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Agora, eliminamos o problema de criar conexão toda hora, o problema do acoplamento, o problema da Lei de Demeter e eliminamos uma classe desnecessária.

 

Entendi agora, você faz/configura a conexão pelo construct da classe que você vai utilizar os métodos, assim faz com que ela seja chamada apenas uma vez para todos os métodos, isso mesmo?

 

Logo a classe ou arquivo de configuração não tem necessidade de existir, apenas o objeto PDO que é passsado como referência em um novo objeto, certo?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Por aí.

 

A classe CRUD depende de um objeto do tipo PDO, declarado aqui:

 

 

function __construct(PDO $pdo){

 

E podemos simplesmente passar uma instância da PDO ao criar uma classe.

 

Dependency Injection. É um conceito simples, mas fantástico. Se todos que trabalham com OO conhecessem não veríamos tanta porcaria de singleton, registry, static e afins por aí.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom Todos comentaram e deram suas dicas, também criei uma classe CRUD quando estava começando a estudar PDO, veja, se puder tire exemplos, agradeço...

 

#Config.php

Padrão de projeto: Registry

class Config {

    public static $data = array();

    public static function write($key, $value) {
        self::$data[$key] = $value;
    }

    public static function read($value) {
        if (!isset(self::$data[$value])) {
            return 'Nao existe ' . self::$data[$value];
        }

        return self::$data[$value];
    }

    public static function checkReg($name) {
        if (array_key_exists($name, self::$data)) {
            return true;
        } else {
            exit('<p>Registro ( ' . $name . ' ) nao Existe...</p>' . __FILE__);
        }
    }

}

 

#database.php

Aplicado o Padrão de projeto: Registry

/**
 * -----------------------------------------------------------------------
 * Constante do ambiente de desenvolvimento, onde pode-se trabalhar com multiplos
 * banco de dados.
 * -----------------------------------------------------------------------
 */
define('ENVIRONMENT','desenvolvimento');

/**
 * -----------------------------------------------------------------------
 * DADOS DE CONEXAO COM BANCO DE DADOS -> PDO
 * -----------------------------------------------------------------------
 * Aqui vocé deve definir suas configurações de banco de dados  de acordo
 * com um determinado ambiente de desenvolvimento. Vocé pode definir quantos
 * ambientes forem necessários.
 * -----------------------------------------------------------------------
 */
Config::write('database', array(
    'desenvolvimento' => array(
        'host' => 'localhost',
        'dbname' => 'tabela-db',
        'user' => 'root',
        'pass' => '',
        'dbdriver' => 'mysql',
        'charset' => 'utf8',
        'options' => null
    ),
    'teste' => array(
        'host' => 'localhost',
        'dbname' => 'mysql',
        'user' => 'root',
        'pass' => '',
        'dbdriver' => 'mysql',
        'charset' => 'utf8',
        'options' => null
    ),
    'producao' => array(
        'host' => 'localhost3',
        'dbname' => 'mysql3',
        'user' => 'root3',
        'pass' => '',
        'dbdriver' => 'mysql',
        'charset' => 'utf8',
        'options' => null
    )
));

 

#Model.php

Aplicando padrão de projeto, singleton, registry, active record, table data gateway.

<?php

class Model {

    protected $db;
    private static $instance;
    public $_tabela;

    public function __construct() {
        $this->db = self::getInstance();
    }

    public static function getInstance() {

        if (is_null(self::$instance)) {
            
            if(Config::checkReg('database')):
                $config = Config::read('database');
            endif;
            
            $host = $config[ENVIRONMENT]['host'];
            $dbname = $config[ENVIRONMENT]['dbname'];
            $dbdriver = $config[ENVIRONMENT]['dbdriver'];
            $username = $config[ENVIRONMENT]['user'];
            $passwd = $config[ENVIRONMENT]['pass'];
            $options = $config[ENVIRONMENT]['options'];
            $charset = $config[ENVIRONMENT]['charset'];
            
            $dsn = $dbdriver . ':host=' . $host . ';dbname=' . $dbname . '';

            self::$instance = new PDO($dsn, $username, $passwd, $options);
            return self::$instance;
        }

        return self::$instance;
    }

    /**
     * Metodo de Inserir no banco de dados
     * @param $dados um array indice=>valor com os dados
     * @return boolean retorna booleano do insert
     */
    public function insert(Array $dados) {
        $campos = implode(',', array_keys($dados));
        $valores = "'" . implode("','", array_values($dados)) . "'";

        $query = $this->db->query("INSERT INTO {$this->_tabela} ({$campos}) VALUES ({$valores})");
        return $query;
    }

    /**
     *  Metodo para leitura de banco de dados
     * @param  string $where   $where clausula de where sql
     * @param  number $limit   limite da consulta
     * @param  number $offset  empura pra frente o limit
     * @param  string $orderby ordenação da consulta
     * @return Array          Array retorna um array associativo
     */
    public function read($where = null, $limit = null, $offset = null, $orderby = null) {
        $where = ($where != null ? "WHERE {$where}" : "");
        $limit = ($limit != null ? "LIMIT {$limit}" : "");
        $offset = ($offset != null ? "OFFSET {$offset}" : "");
        $orderby = ($orderby != null ? "ORDER BY {$orderby}" : "");

        $q = $this->db->query("SELECT * FROM {$this->_tabela} {$where} {$orderby} {$limit} {$offset} ");

        return $q->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Metodo para atualização de dados no Database
     * @param $dados um array indice=>valor com os dados
     * @param $where param que vai gerenciar
     * @return boolean retorna um true ou false
     */
    public function update(Array $dados, $where = null) {
        foreach ($dados as $ind => $val) {
            $campos[] = "{$ind} = '{$val}'";
        }

        $campos = implode(", ", $campos);

        $where = ($where != null ? "WHERE {$where}" : "");
        $query = $this->db->query("UPDATE {$this->_tabela} SET {$campos} {$where} ");

        return $query;
    }

    /**
     * Metodo que deleta uma linha no banco
     * @param $where param que vai gerenciar ex: id=1 
     * @return boolean retorna um true ou false
     */
    public function delete($where = null) {
        $where = ($where != null ? "WHERE {$where}" : "");
        $query = $this->db->query("DELETE FROM {$this->_tabela} {$where} LIMIT 1");

        return $query;
    }

}

#PostModel.php

Trabalhando com a classe, extendendo Model.php onde vai constar todo o CRUD da aplicação!

Já utilizando ActiveRecord e Table Data Gateway que são padrões de projeto interessantes para master uma abstração.

 

<?php

class PostModel extends Model {
    public $_tabela = 'posts';
    
    public function inserir_post($dados = array()){
        $query = $this->insert($dados);
        return $query;
    }
    
    public function listar_post($limit = null){
        $query = $this->read();
        return $query;    
    }
    
    public function atualiza_post($dados = array() , $where){
        $query = $this->update($dados, $where);
        return $query;
    }
    
    public function deletar_post($where){
        $query = $this->delete($where);
        return $query;
    }
    
}

 

#Finalizado

Parei de trabalhar no projeto, mas ainda falta muita coisa para ficar 100% bom, por exemplo:

Não usei Abstract Factory, Interfaces, quis fazer algo simples, limpo e didatico.

 

Abraços, e trabalhe duro para que seu código seja 100% organizado!

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Thadeu, globais cara, não use...

Model singleton??? Hmmm, não é uma boa ideia. Aliás, o que você chama de model não é exatamente um Model, é apenas uma camada de acesso a banco de dados, que está contida no Model.

 

Aqui vai mais um exemplo, utilizando MySQLi invés de PDO:

http://henriquebarcelos.in/blog/2012/08/20/php-oo-classe-simples-para-acesso-a-banco-de-dados/

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.