Ir para conteúdo

POWERED BY:

Arquivado

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

felipe_zmm

Iniciante em OO

Recommended Posts

Você não está dando voltas, está encapsulando, criando tipos, deixando as coisas semânticas. Criar uma classe abstrata e fazer os autenticadores estenderem dela continua violando o SRP, pois o objeto de um autenticador possui ainda a responsabilidade de criptografar. Para que um encriptador padrão? para não deixar o código explícito, aumentar a complexidade?

 

---

Não estou falando que uma API não deve ter documentação, mas ela não deve depender de uma, se ela possui boa nomenclatura e definições, será legível à todos, uma documentação não esconde uma API ruim.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Você não está dando voltas, está encapsulando, criando tipos, deixando as coisas semânticas. Criar uma classe abstrata e fazer os autenticadores estenderem dela continua violando o SRP, pois o objeto de um autenticador possui ainda a responsabilidade de criptografar. Para que um encriptador padrão? para não deixar o código explícito, aumentar a complexidade?

 

A classe abstrata eu pensei em utilizar para resolver o problema da repetição de código das classes autenticadoras, e não o problema das responsabilidades.

 

Quanto ao SRP, no post #53, página 3, eu postei uma modificação das minhas classes disse: A única coisa que eu imagino que ainda viole o SRP seria esse método gerarHash.

 

E você respondeu: "Quando ao SRP, acho que não há mais problemas. Esse gerar hash é algo interno e não chegaria a considerar uma violação ao SRP.".

 

Bom, entenda que eu não estou fazendo nenhum tipo de crítica. Só quero mesmo entender se você apenas mudou de opinião ou se ocorreu alguma mudança tão drástica de lá pra cá. A única diferença que eu enxergo é que antes eu utilizava apenas SHA1 e agora a forma de criptografia varia.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu não queria dar um "tiro" logo no início. Havia outros tópicos para serem abordados, não tinha como se falar de responsabilidade tão a fundo, queria ir com calma. Perceba que até sugeri em um dos posts para você fazer isso, pois queria ir com calma, aos poucos. Veja o código no primeiro post e veja agora, imagine se tudo fosse falado em um post só. Agora que as coisas estão se encaminhando para um código limpo pode-se tranquilamente ser rígido em questões de S.O.L.I.D. e etc., afinal você entendeu o que aconteceu, não foi de uma hora para outra sem explicação alguma.

 

Mesmo você extraindo para uma classe abstrata, ainda existe a responsabilidade única não sendo respeitada, pense na responsabilidade do objeto, não da classe (se não estaríamos usando traits para tudo) e uma dica: evite a herança, ela poucas vezes é uma boa solução.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Enrico, acho que você tem um entendimento errado do SRP.

 

Em suma, uma classe respeita o SRP quando tem apenas um único motivo para se alterar.

:seta: http://www.d80.co.uk/post/2013/03/04/SRP-The-Single-Responsibility-Principle-in-5-minutes.aspx

 

Agora vamos pensar o seguinte: o que está sendo feito ali é fornecer um algoritmo padrão de criptografia para a classe caso o programador não queira escolher um. Você decide o seguinte:

O algoritmo padrão de criptografia será md5.

 

Pronto, acabou, isso não vai mudar. Se alguém quiser um algoritmo diferente, que informe. Quem quiser se manter com o padrão, não precisa nem se preocupar com isso.

 

Ainda é uma violação do SRP?

Compartilhar este post


Link para o post
Compartilhar em outros sites

A violação do SRP está no momento em que os autenticadores têm responsabilidade de criptografar.

 

Usar uma criptografia padrão não viola nada do SRP, mas mesmo assim não vejo motivo para isso, afinal de contas vai criar complexidade.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Aquele método ali serve APENAS pra chamar uma função, da mesma forma que no seu código tem um:

$this->encriptador->criptografar(trim($senha))

Eu poderia definir um método criptografar privado:

private function criptografar($senha) {
    return $this->encriptador->criptografar($senha);
}

Isso não é adicionar responsabilidade, pois é uma DELEGAÇÃO.

 

Bom, não vou mais debater sobre isso, se o Felipe tiver mais dúvidas em relação à implementação, eu volto a participar.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Henrique,

 

Se possível queria sua opinião sobre o uso da classe abstrata, como fiz no post #99.

@felipe_zmm, uma classe abstrata ganha necessidade quando precisamos compartilhar implementação. Sai um pouco fora do escopo de 'programar para uma Interface'. Entra no momento que você já possui suas interfaces bem definidas e começa a criar implementações delas.

 

Suponhamos que você queira fazer uma lib dos elementos químicos. As propriedades dos elementos (número atômico, massa atômica, nome, símbolo) devem ser somente leitura, enquanto estas mesmas propriedades diferem entre si.

 

Não faz o menor sentido implementar [inline]get*()[/inline] em cada um dos elementos. Para isso, nos utilizamos de uma classe abstrata

abstract class Element
{
    protected $name;
    protected $symbol;
    protected $atomic_number;
    protected $atomic_weight;
 
 
    public function getName()
    {
        return $this->name;
    }
 
    public function getSymbol()
    {
        return $this->symbol;
    }
 
    public function getNumber()
    {
        return $this->atomic_number;
    }
 
    public function getWeight()
    {
        return $this->atomic_weight;
    }
}
class Oxygen extends Element
{
    protected $name = 'Oxygen';
    protected $symbol = 'O';
    protected $atomic_number = 8;
    protected $atomic_weight = 15.9994;
}
class Hydrogen extends Element
{
    protected $name = 'Hydrogen';
    protected $symbol = 'H';
    protected $atomic_number = 1;
    protected $atomic_weight = 1.00797;
}

No caso da sua implementação, primeiro, encontramos algumas redundâncias:

 

 



abstract class Autenticador
{
    public function __construct($cripto = null)
    {
        if ($cripto != null)
        {
            $this->cripto = $cripto;
        }
    }
[...]
}

class AutenticadorPdo extends Autenticador
{
    public function __construct(PDO $pdo, $cripto = null)
    {
        if ($cripto != null)
        {
            parent::__construct($cripto);   
        }
            
        $this->pdo = $pdo; 
    }
[...]
}

class AutenticadorTxt extends Autenticador
{
    public function __construct($arquivo, $cripto = null)
    {
        if ($cripto != null)
        {
            parent::__construct($cripto);   
        }
            
        $this->nomeArquivo = $arquivo;
    }
[...]

 

 

 

Creio que você já percebeu algo bem errado aí, não?

 

Agora

 

 




abstract class Autenticador
{
    protected $cripto;
        
    public function __construct($cripto = null)
    {
        if ($cripto != null)
        {
            $this->cripto = $cripto;
        }
    }
        
    public function setCripto($cripto)
    {
        if (!is_callable($cripto))
        {
            throw new Exception('Criptografia inválida!');    
        }
            
        $this->cripto = $cripto;
    }

 

 

 

Vamos supor que você precise realizar alguma interação com o objeto de criptografia, sei lá, rodar um método [inline]initialize[/inline] que faça qualquer coisa obscura que não vem ao caso. Você precisaria adicionar uma linha no construtor e uma linha em [inline]setCripto[/inline]. Muito provavelmente cópia-e-cola.

 

Que tal

public function __construct($cripto = null)
{
    is_null($cripto) or $this->setCripto($cripto);
}
????

 

Quando precisar fazer modificações, você o faz apenas no método adequado. Por isso fazemos assim. Por isso isolamos escopos. Por isso reutilizamos. Iterações no código devem ser realizadas apenas em um ponto específico. Quando você precisa realizar uma refatoração mecânica e repetitiva, isso pode ser sintoma de algum problema.

 

Continuando, você precisou da classe abstrata para compartilhar uma implementação de um acoplamento que você criou. Num dado cenário inicial isso é OK. Apenas atente para o nome.

 

oAuth é um mecanismo de autenticação onde a criptografia não fica sob seu controle. Uma classe [inline]AutenticadorOAuth[/inline] não necessita, necessariamente de uma propriedade [inline]$cripto[/inline]. Ainda assim, [inline]AutenticadorOAuth[/inline] é um [inline]Autenticador[/inline]. Como resolver, então?

 

Lembra que, no começo deste post, disse que a implementação das classes abstratas vem depois da definição das interfaces?? Pois é:

interface Autenticador
{
    public function autenticar();
}
abstract class AutenticadorCriptografado implements Autenticador
{
    const DEFAULT_CRYPTO = 'sha1';
    private $crypto;

    public function __construct(Callable $crypto = null)
    {
        $this->setCrypto($crypto ?: $this::DEFAULT_CRYPTO);
    }

    public function setCrypto(Callable $crypto)
    {
        $this->crypto = $crypto;
    }

    public function getCrypto()
    {
        return $this->crypto;
    }

    public function encrypt($input)
    {
        return call_user_func_array($this->getCrypto(), func_get_args());
    }
}
Veja, também, que o método [inline]autenticar[/inline] não fica amarrado a nome e senha. Se você já instalou aplicações como WhatsApp ou Viber, sabe que, para concluir o registro, é necessário inserir um código recebido via SMS. Isto também é uma forma de autenticação. Desse modo, nossos autenticadores deverão ser primeiro preparados antes de terem o método [inline]autenticar[/inline] requisitado.
class AutenticadorPdo extends AutenticadorCriptografado
{
    const QUERY = "SELECT COUNT(id) FROM usuarios WHERE login = ? AND senha = ? AND status = 1";

    private $connection;
    private $login;
    private $password;
        
    public function setConnection(PDO $connection)
    {
        $this->connection = $connection;
    }

    public function setLogin($login)
    {
        $this->login = strval($login);
    }

    public function setPassword($password)
    {
        $this->password = $this->encrypt($password);
    }

    public function autenticar()
    {
        $stmt = $this->pdo->prepare($this::QUERY);
        $stmt->execute(array($this->login, $this->password));
        if ($stmt->fetchColumn() != 1)
        {
            throw new Exception('Usuário ou senha inválidos!');  
        }
    }
}

Compartilhar este post


Link para o post
Compartilhar em outros sites

@felipe_zmm, uma classe abstrata ganha necessidade quando precisamos compartilhar implementação. Sai um pouco fora do escopo de 'programar para uma Interface'. Entra no momento que você já possui suas interfaces bem definidas e começa a criar implementações delas.

 

Já ouvi bastante essa frase: "Programe para uma interface e não para uma implementação.". Mas acho que eu não entendo bem o que ela significa.

 

 

No caso da sua implementação, primeiro, encontramos algumas redundâncias

 

Você se refere ao fato de eu verificar se é nulo na classe abstrata e depois verificar também nas classes específicas ? Se não é isso confesso que não enxerguei a redundância.

 

 

 

Que tal

public function __construct($cripto = null)
{
    is_null($cripto) or $this->setCripto($cripto);
}

 

Isso é algo que sempre me perguntei na verdade: Devo utilizar o setter no construtor ? Inclusive fiz pesquisas sobre isso mas em geral a resposta que encontrei foi não. Mas na minha cabeça nunca fez sentido ter um setter que faz determinada verificação mas o construtor permitir a entrada de um dado sem essa mesma verificação.

 

 

 

class AutenticadorPdo extends AutenticadorCriptografado
{
    const QUERY = "SELECT COUNT(id) FROM usuarios WHERE login = ? AND senha = ? AND status = 1";

    private $connection;
    private $login;
    private $password;
        
    public function setConnection(PDO $connection)
    {
        $this->connection = $connection;
    }

    public function setLogin($login)
    {
        $this->login = strval($login);
    }

    public function setPassword($password)
    {
        $this->password = $this->encrypt($password);
    }

    public function autenticar()
    {
        $stmt = $this->pdo->prepare($this::QUERY);
        $stmt->execute(array($this->login, $this->password));
        if ($stmt->fetchColumn() != 1)
        {
            throw new Exception('Usuário ou senha inválidos!');  
        }
    }
}

 

Aqui eu entendi o porque de passar login e senha em setters, mas não entendi porque você tirou o construtor e criou um método pra passar a instância do PDO.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Isso é algo que sempre me perguntei na verdade: Devo utilizar o setter no construtor ? Inclusive fiz pesquisas sobre isso mas em geral a resposta que encontrei foi não. Mas na minha cabeça nunca fez sentido ter um setter que faz determinada verificação mas o construtor permitir a entrada de um dado sem essa mesma verificação.

Oi??? Não sei onde você andou pesquisando, mas é mais uma das besteiras que têm por aí. Me aponte UMA razão pela qual não devemos utilizar setters no construtor e, ao invés disso, duplicar o código nele.

 

Quanto ao exemplo do Evandro, eu faria algumas modificações:

class AutenticadorPdo extends AutenticadorCriptografado
{
    private static $query = "SELECT COUNT(#idField#) FROM #table# WHERE #login# = ? AND #senha# = ? AND status = 1";
    
    private $connection;
    private $login;
    private $password;
    private $idField = 'id';
    private $loginField = 'login';
    private $passwdField = 'senha';
    private $table = 'usuarios';

    public function __construct(PDO $connection, $login, $password) {
        $this->setConnection($connection);
        $this->setLogin($login);
        $this->setPassword($password);
    }
        
    public function setConnection(PDO $connection)
    {
        $this->connection = $connection;
    }

    public function setLogin($login)
    {
        $this->login = strval($login);
    }

    public function setPassword($password)
    {
        $this->password = $this->encrypt($password);
    }

    public function autenticar()
    {
        $query = str_replace(
            array('#idField#', '#table#', '#loginField#', '#passwordField#'), 
            array($this->idField, $this->table, $this->loginField, $this->passwdField), 
            self::$query);
        $stmt = $this->pdo->prepare($query);
        $stmt->execute(array($this->$this->login, $this->password));
        if ($stmt->fetchColumn() != 1)
        {
            throw new Exception('Usuário ou senha inválidos!');  
        }
    }
    
    public function setIdField($field) {
        $this->idField = (string) $field;
    }

    public function setTable($table) {
        $this->table = (string) $table;
    }
    // os outros setters aqui...

}

Isso evitará problemas de ter que verificar se usuário e senha foram setados e além disso te permite variar o nome da tabela e dos campo.

 

Agora é um pouco mais reutilizável.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Oi??? Não sei onde você andou pesquisando, mas é mais uma das besteiras que têm por aí. Me aponte UMA razão pela qual não devemos utilizar setters no construtor e, ao invés disso, duplicar o código nele.

 

Não sei se vou me lembrar exatamente porque isso foi antes de criar o tópico, mas as justificativas falavam sobre override do setter em sub classes, e que isso geraria um comportamento inesperado. Eu encontrei essa mesma resposta em uns quatro fóruns diferentes. Mas como eu disse, pra mim nunca fez sentido.

 

Quanto às alterações que você fez na classe do Evandro, eu não sei ao certo sobre fixar valores na classe como você fez com os nomes dos campos e da tabela. Já vi gente dizer que isso atrapalha na reutilização do código enquanto você (e outros) dizem o contrário.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não está perfeito, mas pense que você não precisará mexer na classe se a estrutura da tabela for a mesma, mas se seus campos e nomes variarem.

 

Dá pra viajar mais ali.

Esta implementação contempla apenas um caso muito específico de autenticação através do banco de dados.

Não te permite usar salts, verificar se o usuário já está logado, etc, etc, etc.

 

Eu poderia ficar dando sugestões aqui até amanhã, mas chega uma hora em que "já está bom", e com isso eu quero dizer: já atende 90% dos casos que vou encontrar. Se um dia não for suficiente, aí você refatora.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Esta implementação contempla apenas um caso muito específico de autenticação através do banco de dados.

 

Eu poderia ficar dando sugestões aqui até amanhã, mas chega uma hora em que "já está bom", e com isso eu quero dizer: já atende 90% dos casos que vou encontrar. Se um dia não for suficiente, aí você refatora.

 

Então, mas por isso eu fiz também a classe de autenticação através de um arquivo txt. Na verdade eu acho que eu nunca vou usá-la, mas com isso eu estou tentando me forçar a pensar de forma mais abstrata.

 

Eu tenho lido e relido bastante os posts de vocês e algo que me passou pela cabeça agora foi o seguinte: O Evandro removeu login e senha do método autenticar da interface e os colocou como propriedades da classe AutenticadorPdo com o argumento de que nem toda autenticação precisa deles.

Porém, se eu for implementar isso tudo aqui, quando chegar na classe AutenticadorTxt eu vou novamente ter que colocar as propriedades login e senha, criar os setters e etc. Ou seja, eu estaria novamente entrando no problema da repetição de código.

 

Só se eu criasse a interface Autenticador com obviamente o método autenticar e uma classe abstrata AutenticadorLogin, que teria as propriedades e métodos inerentes a esse tipo de autenticação e caso necessário criar posteriormente outros tipos de autenticadores.

 

Sei-lá se estou viajando mas me passou isso tudo pela cabeça.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então, mas por isso eu fiz também a classe de autenticação através de um arquivo txt. Na verdade eu acho que eu nunca vou usá-la, mas com isso eu estou tentando me forçar a pensar de forma mais abstrata.

Interessante fazer pra aprender, mas não viaje muito. Resolva o seu problema.

Eu tenho lido e relido bastante os posts de vocês e algo que me passou pela cabeça agora foi o seguinte: O Evandro removeu login e senha do método autenticar da interface e os colocou como propriedades da classe AutenticadorPdo com o argumento de que nem toda autenticação precisa deles.

 

Porém, se eu for implementar isso tudo aqui, quando chegar na classe AutenticadorTxt eu vou novamente ter que colocar as propriedades login e senha, criar os setters e etc. Ou seja, eu estaria novamente entrando no problema da repetição de código.

Aí entra o que o Evandro mostrou:

interface Autenticador
{
public function autenticar();
}

abstract class AutenticadorCriptografado implements Autenticador
{
    public function __construct(Criptografador $cripto)
    {
    }
}

abstract class AutenticadorLoginSenha implements Autenticador
{
    public function __construct($login, $senha)
    {
    }
}

Agora e se vc quer um autenticador que funciona com login e senha E TAMBÉM é criptografado.

De primeira, vc emenda:

abstract class AutenticadorLoginSenhaCriptografado implements Autenticador
{
    public function __construct($login, $senha, Criptografador $cripto)
    {
    }
}

Fedeu aí também?

 

Com PHP 5.4 poderíamos utilizar traits pra tentar resolver o problema, mas eu não sou muito familiar com elas pra mostrar um exemplo. Dá pra resolver o problema de outra forma: Decorator.

 

Entendemos que criptografar uma senha é algo adicional a uma autenticação, uma "decoração", certo?

interface Autenticador
{
    public function autenticar();
}

interface AutenticadorSenha extends Autenticador
{
    public function getSenha();
    public function setSenha($senha);
}

abstract class DecoradorAutenticador implements Autenticador {
    private $autenticador;

    public function __construct(Autenticador $auth) {
        $this->setAutenticador($auth);
    }

    // Getters e setters

    // Deixa a implementação de autenticar para as implementações concretas
}
class AutenticadorCriptografadoSenha extends DecoradorAutenticador
{
    private $criptografador;

    public function __construct(Autenticador $auth, Criptografador $cripto)
    {
        parent::__construct($auth);
        $this->setCriptografador($cripto);
    }

    public function autenticar() {
        $auth = $this->getAutenticador()
        if($auth instanceof AutenticadorSenha) {
            $auth->setSenha($this->criptografador->encriptar($auth->getSenha()));
        }
        $auth()->autenticar();
    }
}

Não está 100%, preciso pensar melhor ainda...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Getters e setters são problemáticos (e polêmicos).

 

O primeiro ponto é: você precisa deles? não é algo interno da classe? não seria uma boa jogar no construtor?

 

---

 

O segundo: getters e setters não garantem, veja:

 

<?php

class SomeClass
{
    private $db;

    public function setDb(Db $db)
    {
        $this->db = $db;
    }

    public function getDb()
    {
        return $this->db;
    }

    public function insert($something)
    {
        $this->db->insert($something);
    }
}

 

Se eu não defino o DB, e aí, se executar o insert.... m*****

 

---

 

E outro ponto: lei de demeter, nunca (quase nunca) crie getters que retornam objetos.

 

$obj = new SomeClass;
$obj->getDb()->insert($something);

 

Você fala com estranhos, que você não conhece, e cria uma API complexa.

 

---

 

Por último, cuidado com o modelo anêmico, onde a interface do objeto é genérica, você usa setters e getters quando pode-se possuir uma interface descritiva, que faz operações.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Já ouvi bastante essa frase: "Programe para uma interface e não para uma implementação.". Mas acho que eu não entendo bem o que ela significa.

Significa que, se você arquitetar seu software de forma abstrata, apenas definir a forma como os objetos devem interagir entre si, sem depender de implementação, você consegue uma solução muito mais abrangente e retrocompatível.

 

Já parou pra pensar que você pode estar utilizando funções, vamos dizer, do PHP1?

 

Já parou pra pensar que essas funções podem ter sido refatoradas, modificadas ou até mesmo excluídas?

 

Já parou pra pensar que você não precisa se preocupar se a função foi escrita em C, C++, se usa ou não a ZendEngine do PHP4+, qual versão da ZendEgine está em uso, entre outras coisas?

 

Você não precisa se preocupar com estes detalhes porque você confia na interface do interpretador. Você apenas sabe que as funções estarão ali, disponíveis para uso, independente do que acontece por baixo dos panos.

 

Lembra quando postei um exemplo de DataMapper?

 

Percebeu que eu simplesmente preciso de um DataMapper, sem especificar qual?

 

Um cenário que 'programa para uma Interface, não uma implementação', traz consigo o seguinte comportamento

$user = new User;
$user->mapTo(new MysqlUserMapper);

... Um outro arquivo muito distante ...

$user->getMapper()->persist();
Quando quisermos trocar o banco de MySQL para Postgree, por exemplo, apenas alteramos a linha 2
$user = new User;
$user->mapTo(new PgUserMapper);

... O outro arquivo muito distante permanece intocado ...

$user->getMapper()->persist();

Isso acontece porque ambos os mappers honram a Interface de UserMapper. Dessa forma, podemos variar N implementações a baixíssimo custo.

 

Você se refere ao fato de eu verificar se é nulo na classe abstrata e depois verificar também nas classes específicas ? Se não é isso confesso que não enxerguei a redundância.

Exato. Me refiro a você fazer exatamente a mesma verificação nos métodos construtores das classes abstratas e concretas. É exatamente a mesma linha se repetindo 3 vezes.

 

Idem para definir e obter propriedades. Veja que seu AutenticadorTXT precisa primeiro abrir o recurso para depois utilizá-lo:

 

    public function autenticar($login, $senha)
    {
        $senha = $this->criptografar($senha);
        $this->abrirArquivo();
            
        while (!feof($this->fp))
        {
            $arr = explode(', ', fgets($this->fp, 1024));
              
            if (($login == $arr[0]) && ($senha == trim($arr[1])))
            {
                $this->fecharArquivo();
                return;
            }
        }
            
        $this->fecharArquivo();
        throw new Exception('Usuário ou senha inválidos!');
    }

    private function abrirArquivo()
    {
        $this->fp = fopen($this->nomeArquivo, 'r');
    }
Ignorando o fato que [inline]abrirArquivo[/inline] pode tentar abrir um mesmo arquivo duas vezes, vamos focar no que interessa...

 

Toda vez que você precisar trabalhar com um arquivo precisa:

1. Abrir o arquivo

2. Pegar o recurso para trabalhar

 

Veja que o verdadeiro getter está em 2. Porque não ter um método?

 

    private $path;
    private $resource;

    protected final function getResource($mode = null)
    {
        if (is_resource($this->resource)) {
            return $this->resource;
        }
        if (!touch($this->path)) {
            throw new RuntimeException("Cannot open {$this->path}. Access denied.");
        }
        if (is_null($mode)) {
            $mode = 'w';
        }
        return $this->resource = fopen($this->path, $mode);
    }

 

Isso é algo que sempre me perguntei na verdade: Devo utilizar o setter no construtor ? Inclusive fiz pesquisas sobre isso mas em geral a resposta que encontrei foi não. Mas na minha cabeça nunca fez sentido ter um setter que faz determinada verificação mas o construtor permitir a entrada de um dado sem essa mesma verificação.

Adoraria ler as fontes para debatermos. Acredito que tenha deixado bem explícitas as vantagens nas linhas acima.

 

Aqui eu entendi o porque de passar login e senha em setters, mas não entendi porque você tirou o construtor e criou um método pra passar a instância do PDO.

Mantive a interface. Princípio de substituição de Bárbara Liskov

 

Quando você altera a assinatura de um método sobrescrito, você quebra este princípio. Não há garantia que outras herdeiras de Autenticador possam ser substituídas. Algumas precisam de usuário, senha e uma conexão PDO. Outras precisam de usuário, senha e um arquivo. Outras apenas uma URL openId. Algumas um token de autenticação e assim ad eternum.

 

Quando existe a possibilidade de sobrecarga de métodos - como, por exemplo, no Java -, então você fica livre para implementar outros construtores. Porém, quem fizer uso do construtor mais especializado deve de ter a ciência de que estará aumentando o custo de futuras reimplementações/refatorações.

 

Nota: O manual do PHP, equivocadamente chama sobrescrita de métodos de sobrecarga de métodos. A segunda forma não existe na linguagem até a presente versão (5.5)

Compartilhar este post


Link para o post
Compartilhar em outros sites

E outro ponto: lei de demeter, nunca (quase nunca) crie getters que retornam objetos.

 

Então essa "lei" não se aplica à orientação a objetos.

Se você lida com objetos o tempo todo, como que getters não irão retorná-los?

Quer que eu crie um clone de cada objeto que eu quiser através do getter só pra evitar problemas com efeitos colaterais? E o uso de memória, vai pra onde?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Você não me entendeu haha. Essa "lei" (concordo que não deveria se chamar lei, é arrogante isso) é justamente para a OO.

 

Ela propõe que você só execute métodos de:

 

- do próprio objeto ($this)

- de um objeto que foi passado como argumento para um método

- de um objeto instanciado dentro do método

- de uma propriedade ($this->algumObjeto)

 

A intenção disso é que você diminua a complexidade, veja este exemplo: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Tools/Pagination/Paginator.php#L129

 

O próprio código assume sua falha haha. Veja bem, eu chamo um objeto que chama outro objeto e que chama outro objeto para que eu possa executar uma operação de um "estranho", uma vez que não o conheço (não se enquadram nas 4 formas).

 

Há casos que não se aplica, mas o bom senso prevalece. Eu particularmente tento sempre seguir essa lei, quando não é possível, no máximo eu uso um objeto buscando o outro, exemplo:

 

// aceitável.. dá para melhorar isso
$http->getHeaders()->getRequestURI();

// agghh...
$database->getConnector()->getConnection('production')->getRepository('FooRepository')->findById(2)->getName();

 

E um ponto importante e confuso: fluent interfaces não violam a LOD, pois elas retornam sempre o mesmo objeto.

 

E outro: se o objetivo da classe é fabricar (como são as classes Factory do pattern FactoryMethod) ela não viola a LOD.

 

Edit: um pouco de código, exemplos que seguem a LOD:

 

class Foo {
    public function doX() {}
    public function doY() {
        $this->doX();
    }
}

class Baz {
    public function doZ(Foo $foo) {
        $foo->doX();
        $foo->doX();
    }
}

class Bar {
    public function doW() {
        $baz = new Baz;
        $baz->doZ(new Foo);
    }
}

class FooBarBaz {
    private $bar;

    public function __construct(Bar $bar) {
        $this->bar = $bar;
    }

    // também seria válido:

    public function __construct() {
        $this->bar = new Bar;
    }

    // ...

    public function doV() {
        $this->bar->doW();
    }
}

Compartilhar este post


Link para o post
Compartilhar em outros sites

Getters e setters são problemáticos (e polêmicos).

Concordo com o polêmicos. Não vejo problemas. Vejo efeitos colaterais e consequências.

 

O primeiro ponto é: você precisa deles? não é algo interno da classe? não seria uma boa jogar no construtor?

Mas e o blá blá blá de 3 argumentos por método??? No construtor tá liberado??

 

[inline]$user = new User('Evandro', 'Oliveira', new DateTime('1988-09-21'), Profession::setUp('Developer'), User::MALE, Country::getNacionalityFrom(Country::BRAZIL), ...);[/inline]

 

Escolhe qual dos gurus-vovôs-que-não-programam-em-PHP você vai seguir, poxa. Tá ficando confuso isso.

 

 

O segundo: getters e setters não garantem, veja:

 

 



<?php

class SomeClass
{
    private $db;

    public function setDb(Db $db)
    {
        $this->db = $db;
    }

    public function getDb()
    {
        return $this->db;
    }

    public function insert($something)
    {
        $this->db->insert($something);
    }
}

Se eu não defino o DB, e aí, se executar o insert.... m*****

 

 

 

Se eu não defino o DB, não li a documentação, não tem documentação e eu não li a implementação, tem que dar m. mesmo. Uma boa arquitetura não é aquela à prova de falhas. É a que identifica e aponta a causa delas.

 

Um usuário não deve ver erros. Um desenvolvedor fazendo coisa errada, com certeza!

    public function insert($something)
    {
        $this->getDb()->insert($something);
    }

    protected final function getDb()
    {
        if (is_null($this->db)) {
            throw new BadMethodCallException('Please, set up DB first!');
        }
        return $this->db;
    }

 

Por último, cuidado com o modelo anêmico, onde a interface do objeto é genérica, você usa setters e getters quando pode-se possuir uma interface descritiva, que faz operações.

Essa é a única asserção que eu concordo cegamente. Sempre que possível, envolva o getter/setter num método mais semântico.

 

 

E outro ponto: lei de demeter, nunca (quase nunca) crie getters que retornam objetos.

 

 



$obj = new SomeClass;
$obj->getDb()->insert($something);

Você fala com estranhos, que você não conhece, e cria uma API complexa.

 

 

 

 

 

Você não me entendeu haha. Essa "lei" (concordo que não deveria se chamar lei, é arrogante isso) é justamente para a OO.

 

Ela propõe que você só execute métodos de:

 

- do próprio objeto ($this)

- de um objeto que foi passado como argumento para um método

- de um objeto instanciado dentro do método

- de uma propriedade ($this->algumObjeto)

 

A intenção disso é que você diminua a complexidade, veja este exemplo: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Tools/Pagination/Paginator.php#L129

 

O próprio código assume sua falha haha. Veja bem, eu chamo um objeto que chama outro objeto e que chama outro objeto para que eu possa executar uma operação de um "estranho", uma vez que não o conheço (não se enquadram nas 4 formas).

 

Há casos que não se aplica, mas o bom senso prevalece. Eu particularmente tento sempre seguir essa lei, quando não é possível, no máximo eu uso um objeto buscando o outro, exemplo:

 

 

 



// aceitável.. dá para melhorar isso
$http->getHeaders()->getRequestURI();

// agghh...
$database->getConnector()->getConnection('production')->getRepository('FooRepository')->findById(2)->getName();

E um ponto importante e confuso: fluent interfaces não violam a LOD, pois elas retornam sempre o mesmo objeto.

 

E outro: se o objetivo da classe é fabricar (como são as classes Factory do pattern FactoryMethod) ela não viola a LOD.

 

Edit: um pouco de código, exemplos que seguem a LOD:



class Foo {
    public function doX() {}
    public function doY() {
        $this->doX();
    }
}

class Baz {
    public function doZ(Foo $foo) {
        $foo->doX();
        $foo->doX();
    }
}

class Bar {
    public function doW() {
        $baz = new Baz;
        $baz->doZ(new Foo);
    }
}

class FooBarBaz {
    private $bar;

    public function __construct(Bar $bar) {
        $this->bar = $bar;
    }

    // também seria válido:

    public function __construct() {
        $this->bar = new Bar;
    }

    // ...

    public function doV() {
        $this->bar->doW();
    }
}

 

 

 

 

 

Agora eu te pergunto: Qual criatura, nesse mundo de Deus - em sã consciência - vai chamar e utilizar o retorno de um método/função sem conhecer?!

 

Eu consigo apontar diversas consequências negativas na linha citada

[inline]$platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform();[/inline]

Baixa manutenibilidade, Baixa testabilidade, Baixa reusabilidade, Alto lookup.

 

Mas, de maneira alguma, desconhecimento de API. Principalmente porque os nomes de métodos são semânticos e autodescritivos.

 

Qualquer leigo é capaz de ler e saber que [inline]$countQuery->getEntityManager()[/inline] retorna um EntityManager. Que por sua vez, tem [inline]getConnection()[/inline] que retorna Connection. Por último, [inline]getDatabasePlatform()[/inline] retorna um AbstractPlatform. Se, até agora, você falava com estranhos, estão apresentados.

 

Se formos olhar um pouco mais à frente, notaremos que a variável armazenada tem um único uso direto e, depois, não é mais necessária.

 

Sinceramente, acredito eu que esta quebra foi feita apenas pro código não estourar as 80 colunas e passar no Linter. O método [inline]AbstractPlatform::getSQLResultCasing()[/inline] foi executado/chamado uma única vez e, ao meu ver, nada justificaria o uso de uma variável. Por mais profunda que fosse sua presença na API.

 

Pode ter sido o papa que escreveu a lei que quiser. Pra mim, [inline]$a->b()->c()->d()->e()->f()->...->z()[/inline] é perfeitamente válido se nenhum dos participantes for necessário mais de uma vez.

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.