Ir para conteúdo

Arquivado

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

felipe_zmm

Iniciante em OO

Recommended Posts

Primeiramente, eu não disse para colocar tudo no construtor, isso vai poluir igualmente, mas muitas das coisas são dependências da classe, dependências ficam no construtor.

 

---

 

O problema não é que você fala com estranhos, isso é apenas a forma simples de explicar. O problema é que você possui alta complexidade e alto acoplamento e fica horrível para em um teste unitário por exemplo, criar mocks.

 

Alto acoplamento basta você parar para pensar: x pega y que pega z que pega w que pega v para que então se faça uma ação.

 

---

 

Quanto ao Doctrine, nem li o código por inteiro, estava apenas procurando um exemplo e achei. A ideia só foi mostrar uma violação, que percebi de cara pelo comentário e depois lendo a linha.

 

Quando se cria uma API já complexa vai acontecer isso sempre.

 

---

 

Se $a->b()->c()->d()->e()->f()->...->z() for apenas uma interface fluente, não há problema algum.

 

@off-topic: gurus-vovôs-que-não-programam-em-PHP foi sacanagem hahaha.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

 

Acho que entendi. É mais ou menos o que eu fiz no post #91 na página 5, não ? Eu tenho uma interface Autenticador e as classes AutenticadorPdo e AutenticadoTxt a implementam, sendo que na hora de utilizar eu posso trocar entre as duas sem afetar o funcionamento do sistema.

 

 

 

 

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?

 

Confesso que não entendi. Eu tenho um método, só que ele é privado e bem mais cru do que esse postado por você, até porque inicialmente não é minha intenção fazer essas verificações todas. Mas eles fazem basicamente a mesma coisa com a diferença que você colocou um parâmetro para o modo de acesso ao arquivo.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Galera, andei com pouco tempo pra mexer no código esses dias mas finalmente consegui fazer umas alterações que estavam passando pela minha cabeça. Seguem as classes:

 

interface Autenticador
{
    public function autenticar();    
}
abstract class AutenticadorUsuario implements Autenticador
{
    protected $usuario;
    protected $encriptador;
        
    public function setUsuario(Usuario $usuario)
    {
        $this->usuario = $usuario;
    }
        
    public function setEncriptador(Encriptador $encriptador)
    {
        $this->encriptador = $encriptador;
    }
        
    abstract public function autenticar();
}
class AutenticadorPdo extends AutenticadorUsuario
{
    private $pdo;
    private $campoId    = 'id';
    private $campoLogin = 'login';
    private $campoSenha = 'senha';
    private $nomeTabela = 'usuarios';
        
    private static $query = "SELECT COUNT(#campoId#) FROM #nomeTabela# WHERE #campoLogin# = :login AND #campoSenha# = :senha AND status = '1'";
        
    public function setPdo(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
        
    public function setCampoId($campoId)
    {
        $this->campoId = (string) $campoId;
    }
        
    public function setCampoLogin($campoLogin)
    {
        $this->campoLogin = (string) $campoLogin;
    }
        
    public function setCampoSenha($campoSenha)
    {
        $this->campoSenha = (string) $campoSenha;
    }
        
    public function setNomeTabela($nomeTabela)
    {
        $this->nomeTabela = (string) $nomeTabela;
    }
        
    public function autenticar()
    {
        $query = str_replace(
            array('#campoId#'   , '#nomeTabela#'   , '#campoLogin#'   , '#campoSenha#'),
            array($this->campoId, $this->nomeTabela, $this->campoLogin, $this->campoSenha),
            self::$query);
            
        $stmt = $this->pdo->prepare($query);
        $stmt->bindParam(':login', $this->usuario->getLogin());
        $stmt->bindParam(':senha', $this->encriptador->criptografar(trim($this->usuario->getSenha())));
        $stmt->execute();
            
        if ($stmt->fetchColumn() != 1)
        {
            throw new Exception('Usuário ou senha inválidos!');                
        }
    }
}
class ControleLogin
{
    private $sessao;
       
    public function __construct(Sessao $sessao)
    {
        $this->sessao = $sessao;
    }
        
    public function autenticarLogin(Autenticador $autLogin)
    {
        try
        {
            $autLogin->autenticar();
            $this->sessao->setarValor('usuario', $usuario);
            $this->sessao->setarValor('autenticado', 1);  
        }
        catch (Exception $e)
        {
            throw new Exception($e->getMessage());
        }
    }
        
    public function logout()
    {
        $this->sessao->destruirSessao();  
    }
        
    public function usuarioLogado()
    {
        return $this->sessao->valorSetado('autenticado');
    }
}
interface Encriptador
{
    public function criptografar($input);
}
class SHA1 implements Encriptador
{
    public function criptografar($input)
    {
        echo sha1($input);
        return sha1($input);
    }
}

E utilizando tudo isso:

$usuario = new Usuario();
$usuario->setLogin($_POST['usuario']);
$usuario->setSenha($_POST['senha']);
    
$pdo = new PDO('mysql:host=localhost;dbname=loja', 'root', '');
    
try
{
    $autPdo = new AutenticadorPdo();
    $autPdo->setPdo($pdo);
    $autPdo->setEncriptador(new SHA1());
    $autPdo->setUsuario($usuario);
        
    $cLogin = new ControleLogin(new Sessao(true));
    $cLogin->autenticarLogin($autPdo);
    header("Location: /loja/home_admin.php");   
}
catch (Exception $e)
{
    echo $e->getMessage();
}

 

Bom, quanto às classes referentes ao Autenticador gostaria que me dessem um feedback se estou cometendo algum erro muito grotesco ou se as coisas estão evoluindo. Sei que me disseram pra não utilizar herança inicialmente mas eu não vejo outra forma de evitar a repetição de código que haveria no caso de ter uma outra classe de Autenticador que busque os dados em outra fonte que não um banco de dados.

 

Quanto às classes de Encriptação/Hashing, como eu disse anteriormente eu não me sinto confortável com a ideia de criar classes tão pequenas e que apenas chamam uma função do PHP. Mas como o Henrique Barcelos tinha citado, se eu quisesse implementar um blowfish por exemplo não seria possível da forma como estava antes. Sendo assim resolvi criar a interface Encriptador e classes específicas de cada método de encriptação para tentar resolver esse problema.

 

Enfim, acho que já estou há muito tempo nesse caso do Login, e, a não ser que eu vocês me apontem erros muito graves eu vou pular pra próxima fase e começar a fazer classes mais internas do sistema.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Confesso que não entendi. Eu tenho um método, só que ele é privado e bem mais cru do que esse postado por você, até porque inicialmente não é minha intenção fazer essas verificações todas. Mas eles fazem basicamente a mesma coisa com a diferença que você colocou um parâmetro para o modo de acesso ao arquivo.

Para falar a verdade eu não sei bem o que esta acontecendo, mas creio eu que Evandro fez aquele objeto mais robusto para suprir a necessidade de receber qualquer tipo de arquivo, flexibilidade é requisito para o seu código receber uma nomeclatura de Orientando a Objeto

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

 

class SHA1 implements Encriptador
{
    public function criptografar($input)
    {
        echo sha1($input);
        return sha1($input);
    }
}

 

 

Agora te recomendo comparar o resultado "final" com

Só tira esse echo daí... ehueheueue

Sei que me disseram pra não utilizar herança inicialmente mas eu não vejo outra forma de evitar a repetição de código que haveria no caso de ter uma outra classe de Autenticador que busque os dados em outra fonte que não um banco de dados.

Exatamente isso: JAMAIS faça o que outros mandam sem pensar se está correto ou não. A questão da herança é que muitas vezes você NÃO PRECISA dela, mas é parte fundamental da orientação a objetos.

 

O problema da herança é que ela é totalmente estática, um objeto jamais mudará de tipo durante a execução.

Com a composição, é possível alterar características de um objeto em tempo de execução.

 

No seu caso, o uso da herança está correto.

 

Agora eu te convido a comparar esse código atual com isso aqui.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Agora te recomendo comparar o resultado "final" com

Só tira esse echo daí... ehueheueue

 

Por essas e outras que debugar é melhor, mas às vezes eu tenho preguiça...

 

Não entendi sobre comparar o resultado final. Comparar com o que ?

 

Agora eu te convido a comparar esse código atual com isso aqui.

 

Realmente essa "versão" é muito melhor em todos os sentidos.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Qual a logica dessa classe abstrata ai ?

 

Compartilhar características e métodos com as classes concretas que criei. No caso tenho um AutenticadorPdo e um AutenticadorTxt (não o postei). Sem a classe abstrata eu teria que repetir o mesmo código nas duas classes.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Tente não fazer isso, herança não é legal (não estou falando que é anti-pattern, só que praticamente nunca é a melhor solução).

 

:seta: -

A Orientação a Objetos não é taxonomia, a OO é um relacionamento entre objetos.

A Orientação a Objetos não é vertical, a OO é horizontal.

---

Por que você não usa o construtor ao invés de criar setters? a base da OO é a confiança. Quando você usa setters para definir dependências, você quebra a confiança, veja um exemplo básico:

 

$usuario = new Usuario();
$usuario->setLogin($_POST['usuario']);
$usuario->setSenha($_POST['senha']);
    
$pdo = new PDO('mysql:host=localhost;dbname=loja', 'root', '');
    
try
{
    $autPdo = new AutenticadorPdo();
    $autPdo->setPdo($pdo);
    $autPdo->setEncriptador(new SHA1());
    $autPdo->setUsuario($usuario);
        
    $cLogin = new ControleLogin(new Sessao(true));
    $cLogin->autenticarLogin($autPdo);
    header("Location: /loja/home_admin.php");   
}
catch (Exception $e)
{
    echo $e->getMessage();
}

 

Funciona lindamente e estamos todos felizes :D, mas e agora:

 

 

$usuario = new Usuario();
$usuario->setLogin($_POST['usuario']);
$usuario->setSenha($_POST['senha']);
    
$pdo = new PDO('mysql:host=localhost;dbname=loja', 'root', '');
    
try
{
    $autPdo = new AutenticadorPdo();
    $autPdo->setEncriptador(new SHA1());
    $autPdo->setUsuario($usuario);
        
    $cLogin = new ControleLogin(new Sessao(true));
    $cLogin->autenticarLogin($autPdo);
    header("Location: /loja/home_admin.php");   
}
catch (Exception $e)
{
    echo $e->getMessage();
}

 

:o O código quebrou, é um código frágil, que não se auto valida, PDO é uma dependência, sem PDO, a classe não funciona, trata-se de uma agregação, existem 3 tipos de relacionamento de objetos:

 

 

Associação:

- uma relação não dependente em ambas as partes

- ex: um zoológico possui animais, mas se um animal sair do zoológico, o zoológico não fecha, nem o animal morre, usso é ima associação

 

Composição:

- dependência total, sem essa dependência não funciona, uma parte depende da outra

- ex: homem não funciona sem cérebro, pois necessita dele, sem ele morre, e o cérebro necessita do homem, isso é uma composição

 

Agregação:

- dependência de uma das partes

- ex: carro não funciona sem roda, pois necessita dele, sem ele não anda, mas a roda pode ser usada para outros fins, isso é uma agregação

 

Dependências -> construtor.

 

----

 

Por que você colocou a propriedade query como estática? Cuidado com estado global :devil:.

E qual a lógica do try/catch no autenticarLogin().

 

Vejo esses três pontos como problemas no código, e realmente, o código melhorou muito, não se compara :clap:.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Tente não fazer isso, herança não é legal (não estou falando que é anti-pattern, só que praticamente nunca é a melhor solução).

 

:seta: -

A Orientação a Objetos não é taxonomia, a OO é um relacionamento entre objetos.

A Orientação a Objetos não é horizontal, a OO é vertical.

 

Não seria ao contrário ? Por vertical eu imagino herança.

 

No mais, como eu disse anteriormente, não utilizando herança nesse caso eu começaria a repetir código. Não vejo outra solução.

 

 

Por que você não usa o construtor ao invés de criar setters? a base da OO é a confiança.

 

Concordo com passar as dependências no construtor, já estava pensando em fazer isso. Mas sinceramente eu não consigo entender esse problema que você exemplificou. Se alguém utilizar a classe sem setar o PDO é porque não sabe como ela funciona. Sei-lá, concordo que é melhor no construtor mas não vejo muito sentido no exemplo que você deu.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Tente não fazer isso, herança não é legal (não estou falando que é anti-pattern, só que praticamente nunca é a melhor solução).

Lá vem ele com uma outra frase pronta algum "guru". Não adianta falar "herança não é legal" e não resolver o problema.

 

O mecanismo de Herança é uma das partes mais importantes da orientação a objetos, não "evite" usá-la, mas use-a com responsabilidade.

 

Se alguém utilizar a classe sem setar o PDO é porque não sabe como ela funciona. Sei-lá, concordo que é melhor no construtor mas não vejo muito sentido no exemplo que você deu.

Neste ponto, eu concordo com o Enrico sobre o fato de essa dependência ter que ir no construtor logo de cara.

 

Imagine o seguinte: você está alguns níveis acima na programação, passa esse objeto [inline]AutenticadorPdo[/inline] como parâmetro para algum objeto. Não tem como ele garantir que você chamou [inline]setPdo[/inline] em algum lugar do código. Não seria responsabilidade dele verificar isso, aliás, muitas vezes, ele nem precisa saber que tal método existe.

 

Por que você colocou a propriedade query como estática? Cuidado com estado global

Porque, neste caso, a query é a nível de classe mesmo, não de objeto, não irá variar, poderia até ser uma constante, que também são intrinsecamente estáticas. O problema é que no PHP, constantes de classe são públicas, e essa informação aí não vai ser usada em lugar nenhum, deve ser interna à classe, logo, privada e estática.

 

Para o caso mais simples, onde a estrutura do login não muda, funciona bem. Complica quando você começa a ter um fluxo diferente para autenticar o usuário, mas aí provavelmente surgiriam outras funcionalidades, não somente a query seria usada, o que valeria a implementação de outra classe.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu falei ao contrário, me desculpe, confundi: A Orientação a Objetos não é VERTICAL, a OO é HORIZONTAL.

 

Henrique, poderia editar aquele tópico? provavelmente pode confundir quem for ler depois..

 

---

 

E você poderia jogar o PDO, o User e o Encriptador no construtor de cada classe (lembre-se que um autenticador não é obrigado usar encriptação).

 

---

 

Bem, nesse caso não é para existir uma propriedade, basta uma variável dentro do método ou até mesmo usar a string da query direto.

Compartilhar este post


Link para o post
Compartilhar em outros sites

E você poderia jogar o PDO, o User e o Encriptador no construtor de cada classe (lembre-se que um autenticador não é obrigado usar encriptação).

Neste ponto, eu concordo com o Enrico sobre o fato de essa dependência ter que ir no construtor logo de cara.

Primeiramente, eu não disse para colocar tudo no construtor, isso vai poluir igualmente, mas muitas das coisas são dependências da classe, dependências ficam no construtor.

Queridos, assim como qualquer outro método mágico, construtores são apenas um facilitador. Não há obrigatoriedade das dependências terem de ser resolvidas logo no momento de instância de uma classe.

 

Outro ponto, quando o setup de uma classe, ou conjunto de classes se torna complexo, há uma lacuna pedindo um padrão criacional como Builder e Factory (Abstract e Method), por exemplo.

 

Tomemos por exemplo um Email. As dependências são um remetente, um destinatário e (um assunto ou o corpo de texto).

 

Vejam que um email sem assunto e sem corpo de texto não tem sentido em existir, mas eu posso ter tanto um email com corpo sem assunto, quanto com assunto sem corpo. Dessa forma, essa variação se torna uma dependência forte que não cabe no construtor.

public function __construct(User $from, User $to, $subject = null, $message = null);

public function send()
{
    if (!($this->subject or $this->message)) {
        throw new InvalidMessageException("Empty message.");
    }
    ...
}

Dependências devem ser supridas, ponto. Onde, quando, como ou porque, é outra história. Quando você usa um objeto, você deve conhecer sua Interface. Senão, aí sim, você fala com estranhos.

 

Por exemplo: Se uma consulta SQL depende de um prepared statement, um método prepare deve ser invocado. E você, como utilizador da instância, deve tomar ciência disso. Confiar na Interface não quer dizer que a implementação está apta a trabalhar com qualquer coisa que for jogada dentro dela. Confiar na Interface significa que você sabe como as instâncias se comportam sem precisar saber como o comportamento foi implementado. Interface tem a ver com comportamento.

 

Nota: A implementação a seguir se trata de pseudo-code.

 

class UsesSqlStatement
{
    public function __constructor(Statement $statement)
    {
        $statement->use();
    }
}

interface Statement
{
    public function use();
}

class NotPreparedStatement implements Statement
{
    public function use()
    {
        echo 'Everything OK.';
    }
}

class PreparedStatement implements Statement
{
    private $query;

    public function prepare($query)
    { ... } // $this->query = $query

    public function use()
    {
        if (!$this->query) {
            throw new RuntimeException("I'm not prepared yet!");
        }
        echo 'Weepee!';
    }
}

 

 

Quando eu utilizo uma instância de [inline]UsesSqlStatement[/inline], eu não preciso e nem devo saber se a instância recebida está preparada ou não. Isso é responsabilidade do implementador, de quem está utilizando as instâncias.

 

De igual forma, quando você implementa uma Interface que não prevê definição de atributos (setters), todas as dependências devem ser resolvidas fora do escopo desta interface.

Não seria responsabilidade dele verificar isso, aliás, muitas vezes, ele nem precisa saber que tal método existe.

Complementando, eu apenas utilizo um [inline]Autenticador[/inline]. Aliás, eu apenas preciso de alguém que se comporte como um autenticador fornecendo o método [inline]autenticar()[/inline]. O que este método precisa pra funcionar corretamente, não é da minha conta e eu não vou me preocupar com isso.

 

 

O problema não é que você fala com estranhos, isso é apenas a forma simples de explicar. O problema é que você possui alta complexidade e alto acoplamento e fica horrível para em um teste unitário por exemplo, criar mocks.

 

Alto acoplamento basta você parar para pensar: x pega y que pega z que pega w que pega v para que então se faça uma ação.

Resposta:

Quando se cria uma API já complexa vai acontecer isso sempre.

 

 

Se $a->b()->c()->d()->e()->f()->...->z() for apenas uma interface fluente, não há problema algum.

Há alguns problemas sim. Você já citou dois (que eu já havia citado antes): Alto acoplamento e baixa testabilidade.

 

Fluent Interface - como qualquer outro padrão de design - traz consequências. Se há uma forma fácil de lidar com elas, sigamos em frente. Se o pattern vai trazer mais problemas do que resolver os que já temos, descartemo-no.

 

 

Acho que entendi. É mais ou menos o que eu fiz no post #91 na página 5, não ? Eu tenho uma interface Autenticador e as classes AutenticadorPdo e AutenticadoTxt a implementam, sendo que na hora de utilizar eu posso trocar entre as duas sem afetar o funcionamento do sistema.

Sim. É exatamente aquilo.

 

 

Confesso que não entendi. Eu tenho um método, só que ele é privado e bem mais cru do que esse postado por você, até porque inicialmente não é minha intenção fazer essas verificações todas. Mas eles fazem basicamente a mesma coisa com a diferença que você colocou um parâmetro para o modo de acesso ao arquivo.

Quando você vai trabalhar com arquivos, uma das preocupações que você deve ter é saber se o arquivo está aberto, se há um ponteiro associado a ele:

$meuArquivo = fopen('nome_do_arquivo', 'w');
var_dump(is_resource($meuArquivo);

Dependendo do caso, você pode querer verificar se há permissão de escrita no arquivo/caminho

var_dump(touch('caminho/para/arquivo'))

Novamente, dependendo, pode haver a necessidade de criar o caminho para o arquivo

if (!is_dir('caminho/para/diretorio')) {
    mkdir('caminho/para/diretorio', umask(), true);
}

Agora, se você precisa de tudo isso, vai ficar copiando e colando toda vez que precisar lidar com o arquivo?? Não é mais fácil incluir tudo dentro de um getter e apenas recuperar o recurso, já preparado, validado e aberto?

public functionA()
{
    if (!is_resource($this->file)) {
        $dirname = dirname($this->file);
        if (!is_dir($dirname)) {
            if (!mkdir($dirname, umask(), true)) {
                throw new IOException(sprintf("Can't create dir %s", $dirname));
            }
        }
        $this->file = fopen($this->file, 'w') ?: $this->file;
        if (!is_resource($this->file)) {
            throw new IOException(sprintf("Can't open file %s", $this->file));
        }
    }
    ... // Here comes the *real functionA*
}

public functionB()
{
    if (!is_resource($this->file)) {
        $dirname = dirname($this->file);
        if (!is_dir($dirname)) {
            if (!mkdir($dirname, umask(), true)) {
                throw new IOException(sprintf("Can't create dir %s", $dirname));
            }
        }
        $this->file = fopen($this->file, 'w') ?: $this->file;
        if (!is_resource($this->file)) {
            throw new IOException(sprintf("Can't open file %s", $this->file));
        }
    }
    ... // *functionB*
}

...

Vs.

public functionA()
{
    $file = $this->getFile(); // Test, validate and open
    ...
}

public functionB()
{
    $file = $this->getFile();
    ...
}

Aí você imagina que, mais pra frente, você precisa também testar o MIMEType do arquivo para se certificar que seja um CVS. Você adiciona a verificação em apenas um método! Ao invés de sair procurando todos os que precisam do arquivo e copiar e colar a validação em todos eles.

 

 

Porque, neste caso, a query é a nível de classe mesmo, não de objeto, não irá variar, poderia até ser uma constante, que também são intrinsecamente estáticas. O problema é que no PHP, constantes de classe são públicas, e essa informação aí não vai ser usada em lugar nenhum, deve ser interna à classe, logo, privada e estática.

Se não varia, não vejo problema nenhum na alta visibilidade. Vai continuar pertencendo ao escopo da classe. Mesmo que público, o valor estará protegido enquanto constate.

 

 

Bem, nesse caso não é para existir uma propriedade, basta uma variável dentro do método ou até mesmo usar a string da query direto.

Também é uma abordagem válida. Não sou a pessoa mais indicada para falar sobre baixo nível mas, acredito eu, que a não necessidade de realocamento de memória é mais benéfica.

 

Quando declaramos variáveis, separamos uma parte na memória para aquele valor. Toda vez que a função começa o espaço é separado e, quando a função termina, o espaço é liberado.

 

Por outro lado, quando a string faz parte do escopo externo à função, aquele espaço alocado persiste durante o tempo de vida da string e não da função.

 

Subindo um pouco o escopo, se a string for uma propriedade de instância, persiste durante o tempo de vida da instância. Caso seja uma estática ou constante, persiste até o fim da execução do processo (no caso do PHP).

 

1. Variável dentro do método


...
aloca a query
faz a consulta
desaloca a query
...
aloca a query
faz a consulta
desaloca a query
...
aloca a query
faz a consulta
desaloca a query
...
2. Variável de instância

...
$auth = new AutenticadorPDO; // aloca a query
faz a consulta
faz outra consulta
outra consulta
$auth = qualquer outro valor // desaloca a query
3. Estático ou constante

...
include 'AutenticadorPDO.php'; // aloca a query
$auth = new AutenticadorPDO();
consulta
consulta
consulta
...
...
fim do processo // desaloca a query

Compartilhar este post


Link para o post
Compartilhar em outros sites

Um ponto sobre a lei de Demeter:

 

O problema do $a->b()->c()->d()->e()->f()->...->z() só existe se b retornar um objeto e assim vai, criando uma complexidade alta de ligação de objetos: um objeto lhe oferece um outro que lhe oferece um outro e etc.

 

Fluent Interfaces são apenas um açúcar sintático, elas não possuem problema em relação à complexidade, elas são apenas um atalho:

 

Ao invés de:

<?php

$foo = new Foo;
$foo->addFriend(new Bar);
$foo->addFriend(new Baz);
$foo->addFriend(new FooBarBaz);

 

Podemos usar:

<?php

$foo = new Foo;
$foo->addFriend(new Bar)
    ->addFriend(new Baz)
    ->addFriend(new FooBarBaz);

 

Ela não traz nenhum problema e não viola a "lei" de Demeter e nem nenhum outro princípio.

 

(Eu não gosto muito de Fluent Interfaces, pois não acho elegante).

Compartilhar este post


Link para o post
Compartilhar em outros sites

O problema do $a->b()->c()->d()->e()->f()->...->z() só existe se b retornar um objeto e assim vai

¿Ahn?, não faz sentido... Se b não retornar um objeto, o encadeamento acaba.

Também é uma abordagem válida. Não sou a pessoa mais indicada para falar sobre baixo nível mas, acredito eu, que a não necessidade de realocamento de memória é mais benéfica.

Quanto ao baixo nível da coisa, vale lembrar que em PHP objetos são todos alocados dinamicamente, por isso, ocupam espaço num espaço da memória do processo chamado heap, enquanto que partes estáticas do código (funções, argumentos, classes, métodos e propriedades estáticas) se localizam num espaço chamado stack (pilha).

 

A pilha é preenchida sequencialmente durante a inicialização do script e só é possível alterar elementos do seu topo, isso faz com que a sua operação seja mais rápida. No heap, o acesso é aleatório. Toda vez que é necessário alocar um espaço para um objeto, inicialmente se tenta procurar um espaço de memória contínuo para colocar o novo objeto. Aí vamos lembrar que o PHP possui strings e arrays de tamanho variável, o que acaba complicando ainda mais o gerenciamento do heap, aí eu já não sei dar mais detalhes.

 

Resumindo, pensando apenas em performance, usaríamos o máximo de estáticos possíveis.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Quando eu digo objeto, digo outro objeto, não o $this.

Beleza, o cara que batizou a Fluent Interface, usa como exemplo uma lib que não retorna [inline]this[/inline].

 

A propósito...

Value objects don't have domain-meaningful identity so you can make them and throw them away easily. So the fluency rides on making new values out of old values.

Traduzindo, em [inline]a()->b()->c()[/inline], [inline]b[/inline] pode nem existir mais.

Se discorda disso, o guru-vovô-que-não-programa-em-PHP que escreveu isso é bem acessível.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pessoal, me desculpem pelo extremamente longo tempo sem responder o tópico, mas eu estava atarefado e não tinha como dar a atenção devida aos estudos que abordei aqui.

 

Bom, dando prosseguimento ao mini-sistema que estou fazendo pra aprender OO, montei as seguintes classes:

 

interface Mapper
{
    public function incluir();
    public function atualizar();
    public function excluir();
    public function buscarPorId($id);
    public function listarTodos();   
}
abstract class PdoMapper implements Mapper
{
    protected $pdo;
        
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
}
class UsuarioMapper extends PdoMapper
{
    private $tabela;
    private $usuario;
        
    public function __construct($pdo, $tabela)
    {
        parent::__construct($pdo);
        $this->tabela = $tabela;
    }
        
    public function setUsuario(Usuario $usuario)
    {
        $this->usuario = $usuario;
    }
        
    public function incluir()
    {
        $qry = "INSERT INTO {$this->tabela}(id, login, senha, email, nome, sobrenome, status, data_cadastro) "
             . "VALUES(null, :login, :senha, :email, :nome, :sobrenome, '1', CURDATE())";
            
        $stmt = $this->pdo->prepare($qry);
        $stmt->bindParam(':login', $this->$usuario->getLogin());
        $stmt->bindParam(':senha', $this->$usuario->getSenha());
        $stmt->bindParam(':email', $this->$usuario->getSenha());
        $stmt->bindParam(':nome', $this->$usuario->getNome());
        $stmt->bindParam(':sobrenome', $this->$usuario->getSobrenome());
        $stmt->execute();
                
        $ultimoId = $this->pdo->lastInsertId();
        $usuario->setId($ultimoId);     
    }
        
    public function atualizar()
    {
        $qry = "UPDATE {$this->tabela} SET login = :login, senha = :senha, email = :email, nome = :nome, sobrenome = :sobrenome "
             . "WHERE id = :id";
            
        $stmt = $this->pdo->prepare($qry);
        $stmt->bindParam(':login', $this->$usuario->getLogin());
        $stmt->bindParam('senha', $this->$usuario->getSenha());
        $stmt->bindParam('email', $this->$usuario->getEmail());
        $stmt->bindParam('nome', $this->$usuario->getNome);
        $stmt->bindParam('sobrenome', $this->$usuario->getSobrenome());
        $stmt->execute();
    }
        
    public function excluir()
    {
        $qry = "DELETE FROM {$this->tabela} WHERE id = :id";
            
        $stmt = $this->pdo->prepare($qry);
        $stmt->bindParam(':id', $this->$usuario->getId());
        $stmt->execute();
    }
        
    public function listarTodos()
    {
        $stmt = $this->pdo->query("SELECT id, nome, sobrenome, email, login FROM {$this->tabela} ORDER BY nome");
            
        while($res = $stmt->fetch(PDO::FETCH_OBJ))
        {
            $nUsuario = new Usuario();
            $nUsuario->setId($res->id);
            $nUsuario->setNome($res->nome);
            $nUsuario->setSobrenome($res->sobrenome);
            $nUsuario->setEmail($res->email);
            $nUsuario->setLogin($res->login);
                
            $listaUsuarios[] = $nUsuario;
        }
            
        return $listaUsuarios;
    }
        
    public function buscarPorId($id)
    {
        $qry = "SELECT nome, sobrenome, email, login "
             . "FROM {$this->tabela} "
             . "WHERE id = {$id}";
            
        $stmt = $this->pdo->query($qry);
        $res  = $stmt->fetch(PDO::FETCH_OBJ);
            
        $nUsuario = new Usuario();
        $nUsuario->setNome($res->nome);
        $nUsuario->setSobrenome($res->sobrenome);
        $nUsuario->setEmail($res->email);
        $nUsuario->setLogin($res->login);
            
        return $nUsuario;
    }
    }

Listando usuários:

$pdo = new PDO('mysql:host=localhost;dbname=loja', 'root', '');
$usuarioMapper = new UsuarioMapper($pdo, 'usuarios');
$listaUsuarios = $usuarioMapper->listarTodos();
    
for ($i = 0; $i < count($listaUsuarios); $i++)
{
    echo "<a href='editar_usuario.php?id={$listaUsuarios[$i]->getId()}' title='editar este usuário'>{$listaUsuarios[$i]->getEmail()}</a>";
    echo '<br />';
}

Buscando um usuário para exibição dos dados em formulário:

$id = $_GET['id'];
    
$pdo = new PDO('mysql:host=localhost;dbname=loja', 'root', '');
    
$usuarioMapper = new UsuarioMapper($pdo, 'usuarios');
$usuario = $usuarioMapper->buscarPorId($id);

 

Sei que alguns vão me dizer (como já disseram) que o Mapper não é a melhor opção para se trabalhar com banco de dados por ser muito custoso criar todas as classes e etc, mas é o que eu melhor me adaptei no momento.

 

Quando fui começar a criar essas classes já me atrapalhei logo de cara com a questão da interface. Como discutimos anteriormente neste tópico utilizar interfaces é importante para manter o sistema confiável e coeso, uma vez que dependendo da interface a implementação é o de menos. O problema é que apesar de eu ter criado a interface Mapper, em momento algum neste sistema é esperada uma interface Mapper. Não sei exatamente o que eu deveria ter feito nesse caso.

 

Sobre a herança na classe UsuarioMapper, creio que esteja errada. Deveria criar logo uma classe PdoUsuarioMapper e abolir a classe abstrata PdoMapper ? Mas se eu fizer isso terei que repetir a propriedade $pdo em todos os outros mappers que o sistema venha a ter. Também não sei ao certo como proceder neste caso.

 

Além disso, outras dúvida que me surgiu enquanto criava essas classes é quanto aos parâmetros dos métodos. No caso do método buscarPorId() por exemplo, ele espera um inteiro. Deveria ser um objeto Usuario ? Realmente não sei existe uma regra ou recomendação quanto a isso.

 

Por enquanto é isso. Peço novamente desculpas por ter deixado o tópico tanto tempo sem resposta e agradeço a todos que já responderam e que possam vir a responder.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pessoal, me desculpem pelo extremamente longo tempo sem responder o tópico, mas eu estava atarefado e não tinha como dar a atenção devida aos estudos que abordei aqui.

 

Não precisa de desculpas não ;).

 

----

 

O problema: UsuarioMapper possui um acoplamento com a PDO.

 

Sei que alguns vão me dizer (como já disseram) que o Mapper não é a melhor opção para se trabalhar com banco de dados por ser muito custoso criar todas as classes e etc, mas é o que eu melhor me adaptei no momento.

 

Um padrão só é desaconselhado quando possui efeitos colaterais, ele não é a melhor nem a pior, é apenas uma solução.

 

 

Sobre a herança na classe UsuarioMapper, creio que esteja errada. Deveria criar logo uma classe PdoUsuarioMapper e abolir a classe abstrata PdoMapper ? Mas se eu fizer isso terei que repetir a propriedade $pdo em todos os outros mappers que o sistema venha a ter. Também não sei ao certo como proceder neste caso.

 

Essa classe abstrata é desnecessária. Criar um construtor estúpido que só define uma propriedade é uma coisa totalmente desnecessária, não fique píssico com repetição de código.

 

Além disso, outras dúvida que me surgiu enquanto criava essas classes é quanto aos parâmetros dos métodos. No caso do método buscarPorId() por exemplo, ele espera um inteiro. Deveria ser um objeto Usuario ? Realmente não sei existe uma regra ou recomendação quanto a isso.

 

Usamos esse tipo de objeto para insert/update principalmente, é fácil responder essa sua pergunta com código:

 

 

// Com inteiro

$mapper->buscarPorId(5);

// Com objeto user

$user = new Usuario();
$user->setId(5);

$mapper->buscarPorId($user); // Não maltrate seus dedos, eles não merecem isso =(

 

É meio óbvio essa, com um inteiro o problema é resolvido com uma simplicidade muito maior. KISS.

 

----

 

Meus dois centavos sobre como seria a implementação:

 

<?php

interface UserStorage
{
    public function insert(User $user);
    public function update(User $user);
    public function delete($id);
    public function getAll();
    public function findById($id);
}

class PDOUserStorage implements UserStorage
{
    private $pdo;
    private $table;

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

    public function insert(User $user)
    {
        $query = "INSERT INTO {$this->table} (
            login,
            senha,
            email,
            nome,
            sobrenome,
            status,
            data_cadastro
        ) VALUES (
            :login,
            :senha,
            :email,
            :nome,
            :sobrenome,
            '1',
            CURDATE()
        );";

        $statement = $this->pdo->prepare($query);
        $statement->bindParam(':login', $user->getLogin());
        $statement->bindParam(':senha', $user->getPassword());
        $statement->bindParam(':email', $user->getEmail());
        $statement->bindParam(':nome', $user->getName());
        $statement->bindParam(':sobrenome', $user->getLastName());

        $result = $statement->execute();

        if (! $result) {
            throw new RuntimeException();
        }

        return (int) $this->pdo->lastInsertId();
    }

    public function update(User $user)
    {
        $query = "UPDATE {$this->table} SET
            login = :login,
            senha = :senha,
            email = :email,
            nome = :nome,
            sobrenome = :sobrenome
        WHERE id = :id";

        $statement = $this->pdo->prepare($query);
        $statement->bindParam(':login', $user->getLogin());
        $statement->bindParam(':senha', $user->getPassword());
        $statement->bindParam(':email', $user->getEmail());
        $statement->bindParam(':nome', $user->getName());
        $statement->bindParam(':sobrenome', $user->getLastName());

        if (! $statement->execute()) {
            throw new RuntimeException();
        }
    }

    public function delete($id)
    {
        $query = "DELETE FROM {$this->table} WHERE id = :id";

        $statement = $this->pdo->prepare($query);
        $statement->bindParam(':id', $id);

        if (! $statement->execute()) {
            throw new RuntimeException();
        }
    }

    public function getAll()
    {
        $query = "SELECT id, nome, sobrenome, email, login
        FROM {$this->table}
        ORDER BY nome";

        $statement = $this->pdo->query($query);
        $statement->setFetchMode(PDO::FETCH_CLASS, 'stdClass');

        if (! $statement->execute()) {
            throw new RuntimeException();
        }

        return $statement->fetchAll();
    }

    public function findById($id)
    {
        $query = "SELECT nome, sobrenome, email, login
        FROM {$this->table}
        WHERE id = :id";

        $statement = $this->pdo->query($query);
        $statement->setFetchMode(PDO::FETCH_CLASS, 'stdClass');
        $statement->bindParam(':id', $id);

        if (! $statement->execute()) {
            throw new RuntimeException();
        }
        
        $user = $statement->fetch();
        
        $statement->closeCursor();

        return $user;
    }
}

class User
{
    private $name;
    private $lastName;
    private $email;
    private $login;
    private $password;

    public function __construct($name, $lastName, $email, $login, $password)
    {
        $this->name = $name;
        $this->lastName = $lastName;
        $this->email = $email;
        $this->login = $login;
        $this->password = $password;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getLastName()
    {
        return $this->lastName;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function getLogin()
    {
        return $this->login;
    }

    public function getPassword()
    {
        return $this->password;
    }
}

 

E como um componente poderia usar a nossa engenhoca:

 

class UserManager
{
    private $storage;

    public function __construct(UserStorage $storage)
    {
        $this->storage = $storage;
    }

    public function showUser($id)
    {
        $user = $this->storage->findById($id);
        return sprintf('Mostrando usuário "%s".', $user->name);
    }

    public function showAllUsers()
    {
        $users = $this->storage->getAll();

        $return = '';
        foreach ($users as $user) {
            $return .= sprintf('Este é o usuário "%s" do id #%s.<br />', $user->name, $user->id);
        }
        return $return;
    }
}

// Configuração do Storage
$storage = new PDOUserStorage(new PDO('mysql:host=localhost;dbname=loja', 'root', ''), 'usuarios');

// Criação do camarada fictício que vai mostrar os usuários em forma de texto
$manager = new UserManager($storage);

// Mostrando um usuário
echo $manager->showUser(isset($_GET['id']) ? $_GET['id'] : 0);

// Mostrando todos os usuários
echo $manager->showAllUsers();

 

O que eu recomendo é que você utilize uma ferramenta para banco de dados pronta, é improdutivo e chato ficar fazendo isso toda hora.

 

PS: Eu não testei e deve ter erros.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pessoal, me desculpem pelo extremamente longo tempo sem responder o tópico, mas eu estava atarefado e não tinha como dar a atenção devida aos estudos que abordei aqui.

Todos. Sempre estamos.

Bom, dando prosseguimento ao mini-sistema que estou fazendo pra aprender OO, montei as seguintes classes:



interface Mapper
{
    public function incluir();
    public function atualizar();
    public function excluir();
    public function buscarPorId($id);
    public function listarTodos();   
}

 

 

 

Quando fui começar a criar essas classes já me atrapalhei logo de cara com a questão da interface. Como discutimos anteriormente neste tópico utilizar interfaces é importante para manter o sistema confiável e coeso, uma vez que dependendo da interface a implementação é o de menos. O problema é que apesar de eu ter criado a interface Mapper, em momento algum neste sistema é esperada uma interface Mapper. Não sei exatamente o que eu deveria ter feito nesse caso.

Uma vez que Mapper é um padrão de duas vias, um mapper mapeia algo para alguma coisa. Não existe a interface [inline]Mapper[/inline]. O mínimo que pode existir é [inline]EntryPointMapper[/inline] ou [inline]EndPointMapper[/inline]. É aceitável - apesar de aumentar o acoplamento - ter [inline]EntryToEndPointMapper[/inline].

 

Você deve se perguntar qual dos dois lados tem mais chances de variar. É mais provável que você precise adicionar uma nova entidade ou trocar de banco de dados?

 

 



abstract class PdoMapper implements Mapper
{
    protected $pdo;
        
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }
}

 

 

A assinatura descreve um caso clássico de interface mapper.
interface PdoMapper {
    public function __construct(PDO $connection);
}
Então teríamos [inline]UserToPdoMapper[/inline], [inline]AccountToPdoMapper[/inline], [inline]LogToPdoMapper[/inline], [inline]FriendshipToPdoMapper[/inline], etc, etc, etc.

 

Porém, aqui há um sutil problema. [inline]PDO[/inline] já é uma DAL. Supondo que você implemente as consultas de [inline]PDO[/inline] para MySQL e, depois, precise conectar com Sybase, pode precisar fazer mudanças. Paginação, mesmo, difere até mesmo entre MySQL, MSSQL e Postgres.

 

Você poderia declarar a interface [inline]MysqlMapper[/inline] e poder aceitar tanto [inline]PDO[/inline] quanto [inline]Mysqli[/inline] ou, até mesmo, as famigeradas mysql_*!

 

 



class UsuarioMapper extends PdoMapper
{
...
}

 

 

Sobre a herança na classe UsuarioMapper, creio que esteja errada. Deveria criar logo uma classe PdoUsuarioMapper e abolir a classe abstrata PdoMapper ? Mas se eu fizer isso terei que repetir a propriedade $pdo em todos os outros mappers que o sistema venha a ter. Também não sei ao certo como proceder neste caso.

Péssimo uso de herança!! Encare herança como, herança. Pai pra filho. Uma linhagem familiar. O filho do sr José da Silva será Joãozinho da Silva.

 

Usuário não é um [inline]PDO[/inline]. Usuário usa um [inline]PDO[/inline]. Precisa de um [inline]PDO[/inline].

 

Não tente facilitar a implementação. Não é esse o objetivo da Orientação a Objetos. Se você está preocupado em se repetir, provavelmente há algo errado na modelagem. Se houver reuso de componentes, esse reuso deve ser externado em um novo componente.

Sei que alguns vão me dizer (como já disseram) que o Mapper não é a melhor opção para se trabalhar com banco de dados por ser muito custoso criar todas as classes e etc, mas é o que eu melhor me adaptei no momento.

Não fazer algo por ser "custoso" é desculpa de preguiçoso. Não quer trabalhar, vá ser deputado.

 

 

 

Além disso, outras dúvida que me surgiu enquanto criava essas classes é quanto aos parâmetros dos métodos. No caso do método buscarPorId() por exemplo, ele espera um inteiro. Deveria ser um objeto Usuario ? Realmente não sei existe uma regra ou recomendação quanto a isso.

E se, ao invés de [inline]buscarPorId[/inline], você apenas buscasse?
public function find(User $filter)
{
    $criteria = $this->connection->buildCriteria($filter);
    $query = "select * from users where {$criteria}";
    $statement = $this->connection->prepare($query);
    $statement->execute();
    return $statement->fetchAll();
}

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.